-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Expand file tree
/
Copy pathcontroller_api.py
More file actions
192 lines (164 loc) · 7.25 KB
/
controller_api.py
File metadata and controls
192 lines (164 loc) · 7.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# (c) 2020 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
name: controller_api
author: John Westcott IV (@john-westcott-iv)
short_description: Search the API for objects
requirements:
- None
description:
- Returns GET requests from the Automation Platform Controller API. See
U(https://docs.ansible.com/automation-controller/latest/html/towerapi/) for API usage.
- For use that is cross-compatible between the awx.awx and ansible.controller collection
see the controller_meta module
options:
_terms:
description:
- The endpoint to query, i.e. teams, users, job_templates, etc.
required: True
query_params:
description:
- The query parameters to search for in the form of key/value pairs.
type: dict
required: False
aliases: [query, data, filter, params]
expect_objects:
description:
- Error if the response does not contain either a detail view or a list view.
type: boolean
default: False
aliases: [expect_object]
expect_one:
description:
- Error if the response contains more than one object.
type: boolean
default: False
return_objects:
description:
- If a list view is returned, promote the list of results to the top-level of list returned.
- Allows using this lookup plugin to loop over objects without additional work.
type: boolean
default: True
return_all:
description:
- If the response is paginated, return all pages.
type: boolean
default: False
return_ids:
description:
- If response contains objects, promote the id key to the top-level entries in the list.
- Allows looking up a related object and passing it as a parameter to another module.
- This will convert the return to a string or list of strings depending on the number of selected items.
type: boolean
aliases: [return_id]
default: False
max_objects:
description:
- if C(return_all) is true, this is the maximum of number of objects to return from the list.
- If a list view returns more an max_objects an exception will be raised
type: integer
default: 1000
extends_documentation_fragment: awx.awx.auth_plugin
notes:
- If the query is not filtered properly this can cause a performance impact.
"""
EXAMPLES = """
- name: Load the UI settings
set_fact:
controller_settings: "{{ lookup('awx.awx.controller_api', 'settings/ui') }}"
- name: Load the UI settings specifying the connection info
set_fact:
controller_settings: "{{ lookup('awx.awx.controller_api', 'settings/ui', host='controller.example.com',
username='admin', password=my_pass_var, verify_ssl=False) }}"
- name: Report the usernames of all users with admin privs
debug:
msg: "Admins: {{ query('awx.awx.controller_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}"
- name: debug all organizations in a loop # use query to return a list
debug:
msg: "Organization description={{ item['description'] }} id={{ item['id'] }}"
loop: "{{ query('awx.awx.controller_api', 'organizations') }}"
loop_control:
label: "{{ item['name'] }}"
- name: Make sure user 'john' is an org admin of the default org if the user exists
awx.awx.role:
organization: Default
role: admin
user: john
when: "lookup('awx.awx.controller_api', 'users', query_params={ 'username': 'john' }) | length == 1"
- name: Create an inventory group with all 'foo' hosts
awx.awx.group:
name: "Foo Group"
inventory: "Demo Inventory"
hosts: >-
{{ query(
'awx.awx.controller_api',
'hosts',
query_params={ 'name__startswith' : 'foo', },
) | map(attribute='name') | list }}
register: group_creation
"""
RETURN = """
_raw:
description:
- Response from the API
type: dict
returned: on successful request
"""
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.utils.display import Display
from ..module_utils.controller_api import ControllerAPIModule
class LookupModule(LookupBase):
display = Display()
def handle_error(self, **kwargs):
raise AnsibleError(to_native(kwargs.get('msg')))
def warn_callback(self, warning):
self.display.warning(warning)
def run(self, terms, variables=None, **kwargs):
if len(terms) != 1:
raise AnsibleError('You must pass exactly one endpoint to query')
self.set_options(direct=kwargs)
# Defer processing of params to logic shared with the modules
module_params = {}
for plugin_param, module_param in ControllerAPIModule.short_params.items():
opt_val = self.get_option(plugin_param)
if opt_val is not None:
module_params[module_param] = opt_val
# Create our module
module = ControllerAPIModule(argument_spec={}, direct_params=module_params, error_callback=self.handle_error, warn_callback=self.warn_callback)
response = module.get_endpoint(terms[0], data=self.get_option('query_params', {}))
if 'status_code' not in response:
raise AnsibleError("Unclear response from API: {0}".format(response))
if response['status_code'] != 200:
raise AnsibleError("Failed to query the API: {0}".format(response['json'].get('detail', response['json'])))
return_data = response['json']
if self.get_option('expect_objects') or self.get_option('expect_one'):
if ('id' not in return_data) and ('results' not in return_data):
raise AnsibleError('Did not obtain a list or detail view at {0}, and ' 'expect_objects or expect_one is set to True'.format(terms[0]))
if self.get_option('expect_one'):
if 'results' in return_data and len(return_data['results']) != 1:
raise AnsibleError('Expected one object from endpoint {0}, ' 'but obtained {1} from API'.format(terms[0], len(return_data['results'])))
if self.get_option('return_all') and 'results' in return_data:
if return_data['count'] > self.get_option('max_objects'):
raise AnsibleError(
'List view at {0} returned {1} objects, which is more than the maximum allowed '
'by max_objects, {2}'.format(terms[0], return_data['count'], self.get_option('max_objects'))
)
next_page = return_data['next']
while next_page is not None:
next_response = module.get_endpoint(next_page)
return_data['results'] += next_response['json']['results']
next_page = next_response['json']['next']
return_data['next'] = None
if self.get_option('return_ids'):
if 'results' in return_data:
return_data['results'] = [str(item['id']) for item in return_data['results']]
elif 'id' in return_data:
return_data = str(return_data['id'])
if self.get_option('return_objects') and 'results' in return_data:
return return_data['results']
else:
return [return_data]