From 6d626b3793a37770e19161d0982bfcd01a551db4 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 8 Jun 2020 13:20:32 -0400 Subject: [PATCH 01/22] Adding tower_api and tower_get_id lookup plugins --- .../plugins/doc_fragments/auth_plugin.py | 48 ++++++++ awx_collection/plugins/inventory/tower.py | 27 +---- awx_collection/plugins/lookup/tower_api.py | 110 ++++++++++++++++++ awx_collection/plugins/lookup/tower_get_id.py | 81 +++++++++++++ .../tower_lookup_api_plugin/tasks/main.yml | 84 +++++++++++++ .../tower_lookup_get_id_plugin/tasks/main.yml | 78 +++++++++++++ 6 files changed, 403 insertions(+), 25 deletions(-) create mode 100644 awx_collection/plugins/doc_fragments/auth_plugin.py create mode 100644 awx_collection/plugins/lookup/tower_api.py create mode 100644 awx_collection/plugins/lookup/tower_get_id.py create mode 100644 awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml diff --git a/awx_collection/plugins/doc_fragments/auth_plugin.py b/awx_collection/plugins/doc_fragments/auth_plugin.py new file mode 100644 index 0000000000..25c0b9e8e2 --- /dev/null +++ b/awx_collection/plugins/doc_fragments/auth_plugin.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Wayne Witzel III +# 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 + + +class ModuleDocFragment(object): + + # Ansible Tower documentation fragment + DOCUMENTATION = r''' +options: + host: + description: The network address of your Ansible Tower host. + env: + - name: TOWER_HOST + username: + description: The user that you plan to use to access inventories on Ansible Tower. + env: + - name: TOWER_USERNAME + password: + description: The password for your Ansible Tower user. + env: + - name: TOWER_PASSWORD + oauth_token: + description: + - The Tower OAuth token to use. + env: + - name: TOWER_OAUTH_TOKEN + verify_ssl: + description: + - Specify whether Ansible should verify the SSL certificate of Ansible Tower host. + - Defaults to True, but this is handled by the shared module_utils code + type: bool + env: + - name: TOWER_VERIFY_SSL + aliases: [ validate_certs ] + +notes: +- If no I(config_file) is provided we will attempt to use the tower-cli library + defaults to find your Tower host information. +- I(config_file) should contain Tower configuration in the following format + host=hostname + username=username + password=password +''' diff --git a/awx_collection/plugins/inventory/tower.py b/awx_collection/plugins/inventory/tower.py index c906795a8e..3a650552b6 100644 --- a/awx_collection/plugins/inventory/tower.py +++ b/awx_collection/plugins/inventory/tower.py @@ -19,24 +19,9 @@ DOCUMENTATION = ''' the path in the command would be /path/to/tower_inventory.(yml|yaml). If some arguments in the config file are missing, this plugin will try to fill in missing arguments by reading from environment variables. - If reading configurations from environment variables, the path in the command must be @tower_inventory. + extends_documentation_fragment: + - awx.awx.auth_plugin options: - host: - description: The network address of your Ansible Tower host. - env: - - name: TOWER_HOST - username: - description: The user that you plan to use to access inventories on Ansible Tower. - env: - - name: TOWER_USERNAME - password: - description: The password for your Ansible Tower user. - env: - - name: TOWER_PASSWORD - oauth_token: - description: - - The Tower OAuth token to use. - env: - - name: TOWER_OAUTH_TOKEN inventory_id: description: - The ID of the Ansible Tower inventory that you wish to import. @@ -47,14 +32,6 @@ DOCUMENTATION = ''' env: - name: TOWER_INVENTORY required: True - verify_ssl: - description: - - Specify whether Ansible should verify the SSL certificate of Ansible Tower host. - - Defaults to True, but this is handled by the shared module_utils code - type: bool - env: - - name: TOWER_VERIFY_SSL - aliases: [ validate_certs ] include_metadata: description: Make extra requests to provide all group vars with metadata about the source Ansible Tower host. type: bool diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py new file mode 100644 index 0000000000..b2a366a2c5 --- /dev/null +++ b/awx_collection/plugins/lookup/tower_api.py @@ -0,0 +1,110 @@ +# (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 = """ +lookup: tower_api +author: John Westcott IV (@john-westcott-iv) +short_description: Search the API for objects +requirements: + - None +description: + - Returns GET requests to the Ansible Tower API. See + U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage. +extends_documentation_fragment: + - awx.awx.auth_plugin +options: + _terms: + description: + - The endpoint to query. i.e. teams, users, tokens, job_templates, etc + required: True + query_params: + description: + - The query parameters to search for in the form of key/value pairs. + type: dict + required: True + get_all: + description: + - If the resulting query is pagenated, retriest all pages + - note: If the query is not filtered properly this can cause a performance impact + type: boolean + default: False +""" + +EXAMPLES = """ +- name: Lookup any users who are admins + debug: + msg: "{{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) }}" +""" + +RETURN = """ +_raw: + description: + - Response of objects from API + type: dict + contains: + count: + description: The number of objects your filter returned in total (not necessarally on this page) + type: str + next: + description: The URL path for the next page + type: str + previous: + description: The URL path for the previous page + type: str + results: + description: An array of results that were returned + type: list + returned: on successful create +""" + +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.tower_api import TowerModule + +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') + + # Defer processing of params to logic shared with the modules + module_params = {} + for plugin_param, module_param in TowerModule.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 = TowerModule( + argument_spec={}, direct_params=module_params, + error_callback=self.handle_error, warn_callback=self.warn_callback + ) + + self.set_options(direct=kwargs) + + if self.get_option('get_all'): + return_data = module.get_all_endpoint(terms[0], data=self.get_option('query_params')) + else: + return_data = module.get_endpoint(terms[0], data=self.get_option('query_params')) + with open('/tmp/john', 'w') as f: + import json + f.write(json.dumps(return_data, indent=4)) + + if return_data['status_code'] != 200: + error = return_data + if return_data.get('json', {}).get('detail', False): + error = return_data['json']['detail'] + raise AnsibleError("Failed to query the API: {0}".format(error)) + + return return_data['json'] diff --git a/awx_collection/plugins/lookup/tower_get_id.py b/awx_collection/plugins/lookup/tower_get_id.py new file mode 100644 index 0000000000..34b94fc566 --- /dev/null +++ b/awx_collection/plugins/lookup/tower_get_id.py @@ -0,0 +1,81 @@ +# (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 = """ +lookup: tower_get_id +author: John Westcott IV (@john-westcott-iv) +short_description: Search for a specific ID of an option +requirements: + - None +description: + - Returns an ID of an object found in tower by the fiter criteria. See + U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage. + Raises an exception if not exactly one object is found. +extends_documentation_fragment: + - awx.awx.auth_plugin +options: + _terms: + description: + - The endpoint to query. i.e. teams, users, tokens, job_templates, etc + required: True + query_params: + description: + - The query parameters to search for in the form of key/value pairs. + type: dict + required: True +""" + +EXAMPLES = """ +- name: Lookup a users ID + debug: + msg: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }) }}" +""" + +RETURN = """ +_raw: + description: + - The ID found for the filter criteria + type: str +""" + +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.tower_api import TowerModule + +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') + + # Defer processing of params to logic shared with the modules + module_params = {} + for plugin_param, module_param in TowerModule.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 = TowerModule( + argument_spec={}, direct_params=module_params, + error_callback=self.handle_error, warn_callback=self.warn_callback + ) + + self.set_options(direct=kwargs) + + found_object = module.get_one(terms[0], data=self.get_option('query_params')) + if found_object is None: + self.handle_error(msg='No objects matched that criteria') + else: + return found_object['id'] diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml new file mode 100644 index 0000000000..d57744a506 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -0,0 +1,84 @@ +--- +- name: Generate a random string for test + set_fact: + test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + when: test_id is not defined + +- name: Generate usernames + set_fact: + usernames: + - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}" + +- name: Create all of our users + tower_user: + username: "{{ item }}" + is_superuser: true + password: "{{ test_id }}" + loop: "{{ usernames }}" + register: user_creation_results + +- block: + - name: Test too many params (failure from validation of terms) + set_fact: + junk: "{{ query('awx.awx.tower_api', 'users', 'teams', query_params={}, ) }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'ou must pass exactly one endpoint to query' in result.msg" + + - name: Try to load invalid endpoint + set_fact: + junk: "{{ query('awx.awx.tower_api', 'john', query_params={}, ) }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'The requested object could not be found at' in result.msg" + + - name: Load user of a specific name + set_fact: + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + + - assert: + that: + - users['results'] | length() == 1 + - users['count'] == 1 + + - name: Get the id of the admin users + set_fact: + user_id: "{{ (query('awx.awx.tower_api', 'users', query_params=query_params) | json_query(jmes_query))[0] }}" + vars: + query_params: + username: "{{ user_creation_results['results'][0]['item'] }}" + jmes_query: 'results[*].id' + + - assert: + that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" + + - name: Get a page of users + set_fact: + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'page_size': 2 } ) }}" + + - assert: + that: users['results'] | length() == 2 + + - name: Get all users of a system through next attribute + set_fact: + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'page_size': 1, }, get_all=true ) }}" + + - assert: + that: users['results'] | length() >= 3 + + always: + - name: Cleanup users + tower_user: + username: "{{ item }}" + state: absent + loop: "{{ usernames }}" diff --git a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml new file mode 100644 index 0000000000..4cd71177be --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml @@ -0,0 +1,78 @@ +--- +- name: Generate a random string for test + set_fact: + test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + when: test_id is not defined + +- name: Generate usernames + set_fact: + usernames: + - "AWX-Collection-tests-tower_get_id-user1-{{ test_id }}" + - "AWX-Collection-tests-tower_get_id-user2-{{ test_id }}" + - "AWX-Collection-tests-tower_get_id-user3-{{ test_id }}" + +- name: Create all of our users + tower_user: + username: "{{ item }}" + is_superuser: true + password: "{{ test_id }}" + loop: "{{ usernames }}" + register: user_creation_results + +- block: + - name: Test too many params (failure from validation of terms) + debug: + msg: "{{ query('awx.awx.tower_get_id', 'users', 'teams', query_params={}, ) }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'ou must pass exactly one endpoint to query' in result.msg" + + - name: Try to load invalid endpoint + debug: + msg: "{{ query('awx.awx.tower_get_id', 'john', query_params={}, ) }}" + ignore_errors: true + register: result + + - assert: + that: + - result is failed + - "'The requested object could not be found at' in result.msg" + + - name: Get the ID of the admin user + set_fact: + user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + + - assert: + that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" + + - name: Try to get an ID of someone who does not exist + set_fact: + failed_user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }) }}" + register: results + ignore_errors: true + + - assert: + that: + - results is failed + - "'No objects matched that criteria' in results['msg']" + + - name: Lookup too many users + set_fact: + too_many_user_ids: " {{ query('awx.awx.tower_get_id', 'users', query_params={ 'username__startswith': 'AWX-Collection-tests-tower_get_id-' }) }}" + register: results + ignore_errors: True + + - assert: + that: + - results is failed + - "'An unexpected number of items was returned from the API (3)' in results['msg']" + always: + - name: Cleanup users + tower_user: + username: "{{ item }}" + state: absent + loop: "{{ usernames }}" From 96ae3268a53759a6414d6adbfad1f5ea2006f896 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 9 Jun 2020 07:30:56 -0400 Subject: [PATCH 02/22] Fixing linting issues --- .../tower_lookup_api_plugin/tasks/main.yml | 18 ++++++------ .../tower_lookup_get_id_plugin/tasks/main.yml | 28 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index d57744a506..058716f7df 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -7,15 +7,15 @@ - name: Generate usernames set_fact: usernames: - - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}" - - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}" - - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}" - name: Create all of our users tower_user: - username: "{{ item }}" - is_superuser: true - password: "{{ test_id }}" + username: "{{ item }}" + is_superuser: true + password: "{{ test_id }}" loop: "{{ usernames }}" register: user_creation_results @@ -41,7 +41,7 @@ that: - result is failed - "'The requested object could not be found at' in result.msg" - + - name: Load user of a specific name set_fact: users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" @@ -61,11 +61,11 @@ - assert: that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" - + - name: Get a page of users set_fact: users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'page_size': 2 } ) }}" - + - assert: that: users['results'] | length() == 2 diff --git a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml index 4cd71177be..bf1e5ec765 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml @@ -7,15 +7,15 @@ - name: Generate usernames set_fact: usernames: - - "AWX-Collection-tests-tower_get_id-user1-{{ test_id }}" - - "AWX-Collection-tests-tower_get_id-user2-{{ test_id }}" - - "AWX-Collection-tests-tower_get_id-user3-{{ test_id }}" + - "AWX-Collection-tests-tower_get_id-user1-{{ test_id }}" + - "AWX-Collection-tests-tower_get_id-user2-{{ test_id }}" + - "AWX-Collection-tests-tower_get_id-user3-{{ test_id }}" - name: Create all of our users tower_user: - username: "{{ item }}" - is_superuser: true - password: "{{ test_id }}" + username: "{{ item }}" + is_superuser: true + password: "{{ test_id }}" loop: "{{ usernames }}" register: user_creation_results @@ -25,36 +25,36 @@ msg: "{{ query('awx.awx.tower_get_id', 'users', 'teams', query_params={}, ) }}" ignore_errors: true register: result - + - assert: that: - result is failed - "'ou must pass exactly one endpoint to query' in result.msg" - + - name: Try to load invalid endpoint debug: msg: "{{ query('awx.awx.tower_get_id', 'john', query_params={}, ) }}" ignore_errors: true register: result - + - assert: that: - result is failed - "'The requested object could not be found at' in result.msg" - + - name: Get the ID of the admin user set_fact: user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" - + - assert: that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" - + - name: Try to get an ID of someone who does not exist set_fact: failed_user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }) }}" register: results ignore_errors: true - + - assert: that: - results is failed @@ -64,7 +64,7 @@ set_fact: too_many_user_ids: " {{ query('awx.awx.tower_get_id', 'users', query_params={ 'username__startswith': 'AWX-Collection-tests-tower_get_id-' }) }}" register: results - ignore_errors: True + ignore_errors: true - assert: that: From 6715ea493f948a51a29c87d9ae44ffec09e0c170 Mon Sep 17 00:00:00 2001 From: beeankha Date: Tue, 9 Jun 2020 13:45:45 -0400 Subject: [PATCH 03/22] Fix documentation in api lookup plugin, fix typos in integration tests --- awx_collection/plugins/lookup/tower_api.py | 12 +++++++----- awx_collection/plugins/lookup/tower_get_id.py | 1 + .../targets/tower_lookup_api_plugin/tasks/main.yml | 2 +- .../tower_lookup_get_id_plugin/tasks/main.yml | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index b2a366a2c5..370d477daa 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -8,16 +8,16 @@ lookup: tower_api author: John Westcott IV (@john-westcott-iv) short_description: Search the API for objects requirements: - - None + - None description: - - Returns GET requests to the Ansible Tower API. See + - Returns GET requests from the Ansible Tower API. See U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage. extends_documentation_fragment: - awx.awx.auth_plugin options: _terms: description: - - The endpoint to query. i.e. teams, users, tokens, job_templates, etc + - The endpoint to query, i.e. teams, users, tokens, job_templates, etc. required: True query_params: description: @@ -26,8 +26,9 @@ options: required: True get_all: description: - - If the resulting query is pagenated, retriest all pages - - note: If the query is not filtered properly this can cause a performance impact + - If the resulting query is paginated, return all pages. + - note: If the query is not filtered properly this can cause a performance impact. + - note: In addition, the built in threshold is 10,000 items; if the query returns more an exception will be thrown. type: boolean default: False """ @@ -65,6 +66,7 @@ from ansible.module_utils._text import to_native from ansible.utils.display import Display from ..module_utils.tower_api import TowerModule + class LookupModule(LookupBase): display = Display() diff --git a/awx_collection/plugins/lookup/tower_get_id.py b/awx_collection/plugins/lookup/tower_get_id.py index 34b94fc566..69b4575c8e 100644 --- a/awx_collection/plugins/lookup/tower_get_id.py +++ b/awx_collection/plugins/lookup/tower_get_id.py @@ -46,6 +46,7 @@ from ansible.module_utils._text import to_native from ansible.utils.display import Display from ..module_utils.tower_api import TowerModule + class LookupModule(LookupBase): display = Display() diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 058716f7df..1056c02d3f 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -29,7 +29,7 @@ - assert: that: - result is failed - - "'ou must pass exactly one endpoint to query' in result.msg" + - "'You must pass exactly one endpoint to query' in result.msg" - name: Try to load invalid endpoint set_fact: diff --git a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml index bf1e5ec765..8d19f836c6 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml @@ -29,7 +29,7 @@ - assert: that: - result is failed - - "'ou must pass exactly one endpoint to query' in result.msg" + - "'You must pass exactly one endpoint to query' in result.msg" - name: Try to load invalid endpoint debug: From 3c0226598664daa1b25f2a725148f6d3edc255f2 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 9 Jun 2020 14:11:46 -0400 Subject: [PATCH 04/22] Fixing doc issue --- awx_collection/plugins/lookup/tower_api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 370d477daa..f849d78f77 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -27,10 +27,12 @@ options: get_all: description: - If the resulting query is paginated, return all pages. - - note: If the query is not filtered properly this can cause a performance impact. - - note: In addition, the built in threshold is 10,000 items; if the query returns more an exception will be thrown. type: boolean default: False + +notes: + - If the query is not filtered properly this can cause a performance impact. + - In addition, the built in threshold is 10,000 items; if the query returns more an exception will be thrown. """ EXAMPLES = """ From 44a3057d16ae202edc4c3e4cede74dcd3c2c42cb Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Wed, 10 Jun 2020 14:54:19 -0400 Subject: [PATCH 05/22] Initial fixes from github comments --- awx_collection/plugins/lookup/tower_get_id.py | 4 ++-- .../tower_lookup_api_plugin/tasks/main.yml | 19 +++++-------------- .../tower_lookup_get_id_plugin/tasks/main.yml | 4 ++-- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_get_id.py b/awx_collection/plugins/lookup/tower_get_id.py index 69b4575c8e..7712f53e89 100644 --- a/awx_collection/plugins/lookup/tower_get_id.py +++ b/awx_collection/plugins/lookup/tower_get_id.py @@ -30,13 +30,13 @@ options: EXAMPLES = """ - name: Lookup a users ID debug: - msg: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }) }}" + msg: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username': 'admin' }) }}" """ RETURN = """ _raw: description: - - The ID found for the filter criteria + - The ID found for the filter criteria returned as a string (i.e. "42" instead of 42). type: str """ diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 1056c02d3f..60e679ee35 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -50,31 +50,22 @@ that: - users['results'] | length() == 1 - users['count'] == 1 - - - name: Get the id of the admin users - set_fact: - user_id: "{{ (query('awx.awx.tower_api', 'users', query_params=query_params) | json_query(jmes_query))[0] }}" - vars: - query_params: - username: "{{ user_creation_results['results'][0]['item'] }}" - jmes_query: 'results[*].id' - - - assert: - that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" + - users['results'][0]['id'] == user_creation_results['results'][0]['id'] - name: Get a page of users set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'page_size': 2 } ) }}" + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 } ) }}" - assert: that: users['results'] | length() == 2 - name: Get all users of a system through next attribute set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'page_size': 1, }, get_all=true ) }}" + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, get_all=true ) }}" - assert: - that: users['results'] | length() >= 3 + that: + - users['results'] | length() >= 3 always: - name: Cleanup users diff --git a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml index 8d19f836c6..78961be0b5 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml @@ -42,7 +42,7 @@ - result is failed - "'The requested object could not be found at' in result.msg" - - name: Get the ID of the admin user + - name: Get the ID of the first user created set_fact: user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" @@ -62,7 +62,7 @@ - name: Lookup too many users set_fact: - too_many_user_ids: " {{ query('awx.awx.tower_get_id', 'users', query_params={ 'username__startswith': 'AWX-Collection-tests-tower_get_id-' }) }}" + too_many_user_ids: " {{ query('awx.awx.tower_get_id', 'users', query_params={ 'username__endswith': test_id }) }}" register: results ignore_errors: true From f4454a6c931a9783e7a5054a610e76c8d9b81cb4 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 11 Jun 2020 13:34:40 -0400 Subject: [PATCH 06/22] Make tower_api a generic GET'er --- awx_collection/plugins/lookup/tower_api.py | 30 ++++++------------- .../plugins/module_utils/tower_api.py | 5 +++- .../tower_lookup_api_plugin/tasks/main.yml | 20 +++++++++++++ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index f849d78f77..e07295a724 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -23,7 +23,7 @@ options: description: - The query parameters to search for in the form of key/value pairs. type: dict - required: True + required: False get_all: description: - If the resulting query is paginated, return all pages. @@ -36,6 +36,10 @@ notes: """ EXAMPLES = """ +- name: Load the UI settings + debug: + msg: "{{ query('awx.awx.tower_api', 'settings/ui') }}" + - name: Lookup any users who are admins debug: msg: "{{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) }}" @@ -44,22 +48,9 @@ EXAMPLES = """ RETURN = """ _raw: description: - - Response of objects from API + - Response from the API type: dict - contains: - count: - description: The number of objects your filter returned in total (not necessarally on this page) - type: str - next: - description: The URL path for the next page - type: str - previous: - description: The URL path for the previous page - type: str - results: - description: An array of results that were returned - type: list - returned: on successful create + returned: on successful request """ from ansible.plugins.lookup import LookupBase @@ -98,12 +89,9 @@ class LookupModule(LookupBase): self.set_options(direct=kwargs) if self.get_option('get_all'): - return_data = module.get_all_endpoint(terms[0], data=self.get_option('query_params')) + return_data = module.get_all_endpoint(terms[0], data=self.get_option('query_params', {} ), none_is_not_fatal=True) else: - return_data = module.get_endpoint(terms[0], data=self.get_option('query_params')) - with open('/tmp/john', 'w') as f: - import json - f.write(json.dumps(return_data, indent=4)) + return_data = module.get_endpoint(terms[0], data=self.get_option('query_params', {} )) if return_data['status_code'] != 200: error = return_data diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index d0120ec003..1a911d96b1 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -274,7 +274,10 @@ class TowerModule(AnsibleModule): def get_all_endpoint(self, endpoint, *args, **kwargs): response = self.get_endpoint(endpoint, *args, **kwargs) if 'next' not in response['json']: - raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response)) + if not 'none_is_not_fatal' in kwargs or not kwargs['none_is_not_fatal']: + raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response)) + else: + return response next_page = response['json']['next'] if response['json']['count'] > 10000: diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 60e679ee35..584f730514 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -67,6 +67,26 @@ that: - users['results'] | length() >= 3 + - name: Get the settings page + set_fact: + settings: "{{ query('awx.awx.tower_api', 'settings/ui' ) }}" + register: results + + - assert: + that: + - results is succeeded + - "'CUSTOM_LOGO' in settings" + + - name: Get the ping page + set_fact: + ping_data: "{{ query('awx.awx.tower_api', 'ping' ) }}" + register: results + + - assert: + that: + - results is succeeded + - "'active_node' in ping_data" + always: - name: Cleanup users tower_user: From 30ff112c87749ccbe375a30c2264cb1a38219084 Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 18 Jun 2020 12:44:39 -0400 Subject: [PATCH 07/22] Pull in functionality from lookup plugin get_id into tower_api itself --- awx_collection/plugins/lookup/tower_api.py | 92 ++++++++++++++++--- .../plugins/module_utils/tower_api.py | 5 +- .../tower_lookup_api_plugin/tasks/main.yml | 66 +++++++++---- 3 files changed, 129 insertions(+), 34 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index e07295a724..00205e0146 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -24,11 +24,41 @@ options: - The query parameters to search for in the form of key/value pairs. type: dict required: False - get_all: + aliases: [query, data, filter, params] + expect_objects: description: - - If the resulting query is paginated, return all pages. + - 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. + 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. + type: integer + default: 1000 notes: - If the query is not filtered properly this can cause a performance impact. @@ -88,15 +118,51 @@ class LookupModule(LookupBase): self.set_options(direct=kwargs) - if self.get_option('get_all'): - return_data = module.get_all_endpoint(terms[0], data=self.get_option('query_params', {} ), none_is_not_fatal=True) + 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(return_data.get('detail', return_data))) + + 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'] = [item['id'] for item in return_data['results']] + elif 'id' in return_data: + return_data = return_data['id'] + + if self.get_option('return_objects') and 'results' in return_data: + return return_data['results'] else: - return_data = module.get_endpoint(terms[0], data=self.get_option('query_params', {} )) - - if return_data['status_code'] != 200: - error = return_data - if return_data.get('json', {}).get('detail', False): - error = return_data['json']['detail'] - raise AnsibleError("Failed to query the API: {0}".format(error)) - - return return_data['json'] + return [return_data] diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 1a911d96b1..d0120ec003 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -274,10 +274,7 @@ class TowerModule(AnsibleModule): def get_all_endpoint(self, endpoint, *args, **kwargs): response = self.get_endpoint(endpoint, *args, **kwargs) if 'next' not in response['json']: - if not 'none_is_not_fatal' in kwargs or not kwargs['none_is_not_fatal']: - raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response)) - else: - return response + raise RuntimeError('Expected list from API at {0}, got: {1}'.format(endpoint, response)) next_page = response['json']['next'] if response['json']['count'] > 10000: diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 584f730514..06d2d18ca9 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -42,9 +42,9 @@ - result is failed - "'The requested object could not be found at' in result.msg" - - name: Load user of a specific name + - name: Load user of a specific name without promoting objects set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + users: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}" - assert: that: @@ -52,34 +52,66 @@ - users['count'] == 1 - users['results'][0]['id'] == user_creation_results['results'][0]['id'] - - name: Get a page of users - set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 } ) }}" + - name: Loop over one user with the loop syntax + assert: + that: + - item['id'] == user_creation_results['results'][0]['id'] + loop: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + loop_control: + label: "{{ item['id'] }}" - - assert: - that: users['results'] | length() == 2 + - name: Get a page of users as just ids + set_fact: + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}" + + - name: Assert that user list has 2 integer ids only + assert: + that: + - users | length() == 2 + - user_creation_results['results'][0]['id'] in users - name: Get all users of a system through next attribute set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, get_all=true ) }}" + users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}" - assert: that: - - users['results'] | length() >= 3 + - users | length() >= 3 + + - name: Get the ID of the first user created and verify that it is correct + assert: + that: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True) }}[0] == {{ user_creation_results['results'][0]['id'] }}" + + - name: Try to get an ID of someone who does not exist + set_fact: + failed_user_id: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}" + register: result + ignore_errors: true + + - assert: + that: + - result is failed + - "'Expected one object from endpoint users' in result['msg']" + + - name: Lookup too many users + set_fact: + too_many_user_ids: " {{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}" + register: results + ignore_errors: true + + - assert: + that: + - results is failed + - "'Expected one object from endpoint users, but obtained 3' in results['msg']" - name: Get the settings page - set_fact: - settings: "{{ query('awx.awx.tower_api', 'settings/ui' ) }}" - register: results - - - assert: + assert: that: - - results is succeeded - - "'CUSTOM_LOGO' in settings" + - "'CUSTOM_LOGO' in {{ lookup('awx.awx.tower_api', 'settings/ui' ) }}" - name: Get the ping page set_fact: - ping_data: "{{ query('awx.awx.tower_api', 'ping' ) }}" + ping_data: "{{ lookup('awx.awx.tower_api', 'ping' ) }}" register: results - assert: From 8239232d4df3e7658a30e95d94171c9466884d8e Mon Sep 17 00:00:00 2001 From: AlanCoding Date: Thu, 18 Jun 2020 12:45:37 -0400 Subject: [PATCH 08/22] Delete the tower_get_id plugin and tests --- awx_collection/plugins/lookup/tower_get_id.py | 82 ------------------- .../tower_lookup_get_id_plugin/tasks/main.yml | 78 ------------------ 2 files changed, 160 deletions(-) delete mode 100644 awx_collection/plugins/lookup/tower_get_id.py delete mode 100644 awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml diff --git a/awx_collection/plugins/lookup/tower_get_id.py b/awx_collection/plugins/lookup/tower_get_id.py deleted file mode 100644 index 7712f53e89..0000000000 --- a/awx_collection/plugins/lookup/tower_get_id.py +++ /dev/null @@ -1,82 +0,0 @@ -# (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 = """ -lookup: tower_get_id -author: John Westcott IV (@john-westcott-iv) -short_description: Search for a specific ID of an option -requirements: - - None -description: - - Returns an ID of an object found in tower by the fiter criteria. See - U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage. - Raises an exception if not exactly one object is found. -extends_documentation_fragment: - - awx.awx.auth_plugin -options: - _terms: - description: - - The endpoint to query. i.e. teams, users, tokens, job_templates, etc - required: True - query_params: - description: - - The query parameters to search for in the form of key/value pairs. - type: dict - required: True -""" - -EXAMPLES = """ -- name: Lookup a users ID - debug: - msg: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username': 'admin' }) }}" -""" - -RETURN = """ -_raw: - description: - - The ID found for the filter criteria returned as a string (i.e. "42" instead of 42). - type: str -""" - -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.tower_api import TowerModule - - -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') - - # Defer processing of params to logic shared with the modules - module_params = {} - for plugin_param, module_param in TowerModule.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 = TowerModule( - argument_spec={}, direct_params=module_params, - error_callback=self.handle_error, warn_callback=self.warn_callback - ) - - self.set_options(direct=kwargs) - - found_object = module.get_one(terms[0], data=self.get_option('query_params')) - if found_object is None: - self.handle_error(msg='No objects matched that criteria') - else: - return found_object['id'] diff --git a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml deleted file mode 100644 index 78961be0b5..0000000000 --- a/awx_collection/tests/integration/targets/tower_lookup_get_id_plugin/tasks/main.yml +++ /dev/null @@ -1,78 +0,0 @@ ---- -- name: Generate a random string for test - set_fact: - test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - when: test_id is not defined - -- name: Generate usernames - set_fact: - usernames: - - "AWX-Collection-tests-tower_get_id-user1-{{ test_id }}" - - "AWX-Collection-tests-tower_get_id-user2-{{ test_id }}" - - "AWX-Collection-tests-tower_get_id-user3-{{ test_id }}" - -- name: Create all of our users - tower_user: - username: "{{ item }}" - is_superuser: true - password: "{{ test_id }}" - loop: "{{ usernames }}" - register: user_creation_results - -- block: - - name: Test too many params (failure from validation of terms) - debug: - msg: "{{ query('awx.awx.tower_get_id', 'users', 'teams', query_params={}, ) }}" - ignore_errors: true - register: result - - - assert: - that: - - result is failed - - "'You must pass exactly one endpoint to query' in result.msg" - - - name: Try to load invalid endpoint - debug: - msg: "{{ query('awx.awx.tower_get_id', 'john', query_params={}, ) }}" - ignore_errors: true - register: result - - - assert: - that: - - result is failed - - "'The requested object could not be found at' in result.msg" - - - name: Get the ID of the first user created - set_fact: - user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" - - - assert: - that: "{{ user_id }} == {{ user_creation_results['results'][0]['id'] }}" - - - name: Try to get an ID of someone who does not exist - set_fact: - failed_user_id: "{{ query('awx.awx.tower_get_id', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }) }}" - register: results - ignore_errors: true - - - assert: - that: - - results is failed - - "'No objects matched that criteria' in results['msg']" - - - name: Lookup too many users - set_fact: - too_many_user_ids: " {{ query('awx.awx.tower_get_id', 'users', query_params={ 'username__endswith': test_id }) }}" - register: results - ignore_errors: true - - - assert: - that: - - results is failed - - "'An unexpected number of items was returned from the API (3)' in results['msg']" - always: - - name: Cleanup users - tower_user: - username: "{{ item }}" - state: absent - loop: "{{ usernames }}" From 34c23caed3366f2296f5bbce1039dcc85fd7f87b Mon Sep 17 00:00:00 2001 From: Alan Rominger Date: Thu, 2 Jul 2020 12:11:16 -0400 Subject: [PATCH 09/22] make lookup plugin compatible with ansible.tower namespace (#9) --- awx_collection/plugins/inventory/tower.py | 59 +++++++------- awx_collection/plugins/lookup/tower_api.py | 5 +- awx_collection/plugins/modules/tower_meta.py | 78 +++++++++++++++++++ .../tower_lookup_api_plugin/tasks/main.yml | 25 +++--- .../roles/template_galaxy/tasks/main.yml | 11 ++- 5 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 awx_collection/plugins/modules/tower_meta.py diff --git a/awx_collection/plugins/inventory/tower.py b/awx_collection/plugins/inventory/tower.py index 3a650552b6..872e2a3328 100644 --- a/awx_collection/plugins/inventory/tower.py +++ b/awx_collection/plugins/inventory/tower.py @@ -6,36 +6,35 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = ''' - name: tower - plugin_type: inventory - author: - - Matthew Jones (@matburt) - - Yunfan Zhang (@YunfanZhang42) - short_description: Ansible dynamic inventory plugin for Ansible Tower. - description: - - Reads inventories from Ansible Tower. - - Supports reading configuration from both YAML config file and environment variables. - - If reading from the YAML file, the file name must end with tower.(yml|yaml) or tower_inventory.(yml|yaml), - the path in the command would be /path/to/tower_inventory.(yml|yaml). If some arguments in the config file - are missing, this plugin will try to fill in missing arguments by reading from environment variables. - - If reading configurations from environment variables, the path in the command must be @tower_inventory. - extends_documentation_fragment: - - awx.awx.auth_plugin - options: - inventory_id: - description: - - The ID of the Ansible Tower inventory that you wish to import. - - This is allowed to be either the inventory primary key or its named URL slug. - - Primary key values will be accepted as strings or integers, and URL slugs must be strings. - - Named URL slugs follow the syntax of "inventory_name++organization_name". - type: raw - env: - - name: TOWER_INVENTORY - required: True - include_metadata: - description: Make extra requests to provide all group vars with metadata about the source Ansible Tower host. - type: bool - default: False +name: tower +plugin_type: inventory +author: + - Matthew Jones (@matburt) + - Yunfan Zhang (@YunfanZhang42) +short_description: Ansible dynamic inventory plugin for Ansible Tower. +description: + - Reads inventories from Ansible Tower. + - Supports reading configuration from both YAML config file and environment variables. + - If reading from the YAML file, the file name must end with tower.(yml|yaml) or tower_inventory.(yml|yaml), + the path in the command would be /path/to/tower_inventory.(yml|yaml). If some arguments in the config file + are missing, this plugin will try to fill in missing arguments by reading from environment variables. + - If reading configurations from environment variables, the path in the command must be @tower_inventory. +extends_documentation_fragment: awx.awx.auth_plugin +options: + inventory_id: + description: + - The ID of the Ansible Tower inventory that you wish to import. + - This is allowed to be either the inventory primary key or its named URL slug. + - Primary key values will be accepted as strings or integers, and URL slugs must be strings. + - Named URL slugs follow the syntax of "inventory_name++organization_name". + type: raw + env: + - name: TOWER_INVENTORY + required: True + include_metadata: + description: Make extra requests to provide all group vars with metadata about the source Ansible Tower host. + type: bool + default: False ''' EXAMPLES = ''' diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 00205e0146..63e3d30be1 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -12,8 +12,9 @@ requirements: description: - Returns GET requests from the Ansible Tower API. See U(https://docs.ansible.com/ansible-tower/latest/html/towerapi/index.html) for API usage. -extends_documentation_fragment: - - awx.awx.auth_plugin + - For use that is cross-compatible between the awx.awx and ansible.tower collection + see the tower_meta module +extends_documentation_fragment: awx.awx.auth_plugin options: _terms: description: diff --git a/awx_collection/plugins/modules/tower_meta.py b/awx_collection/plugins/modules/tower_meta.py new file mode 100644 index 0000000000..34b4453c18 --- /dev/null +++ b/awx_collection/plugins/modules/tower_meta.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: tower_meta +author: "Alan Rominger (@alancoding)" +short_description: Returns metadata about the collection this module lives in. +description: + - Allows a user to find out what collection this module exists in. + - This takes common module parameters, but does nothing with them. +options: {} +extends_documentation_fragment: awx.awx.auth +''' + + +RETURN = ''' +prefix: + description: Collection namespace and name in the namespace.name format + returned: success + sample: awx.awx +name: + description: Collection name + returned: success + sample: awx +namespace: + description: Collection namespace + returned: success + sample: awx +version: + description: Version of the collection + returned: success + sample: 0.0.1-devel +''' + + +EXAMPLES = ''' +- tower_meta: + register: result + +- name: Show details about the collection + debug: var=result + +- name: Load the UI setting without hard-coding the collection name + debug: + msg: "{{ lookup(result.prefix + '.tower_api', 'settings/ui') }}" +''' + + +from ..module_utils.tower_api import TowerModule + + +def main(): + module = TowerModule(argument_spec={}) + namespace = { + 'awx': 'awx', + 'tower': 'ansible' + }.get(module._COLLECTION_TYPE, 'unknown') + namespace_name = '{0}.{1}'.format(namespace, module._COLLECTION_TYPE) + module.exit_json( + prefix=namespace_name, + name=module._COLLECTION_TYPE, + namespace=namespace, + version=module._COLLECTION_VERSION + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 06d2d18ca9..36325fe9df 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -19,10 +19,13 @@ loop: "{{ usernames }}" register: user_creation_results +- tower_meta: + register: tower_meta + - block: - name: Test too many params (failure from validation of terms) set_fact: - junk: "{{ query('awx.awx.tower_api', 'users', 'teams', query_params={}, ) }}" + junk: "{{ query(tower_meta.prefix + '.tower_api', 'users', 'teams', query_params={}, ) }}" ignore_errors: true register: result @@ -33,7 +36,7 @@ - name: Try to load invalid endpoint set_fact: - junk: "{{ query('awx.awx.tower_api', 'john', query_params={}, ) }}" + junk: "{{ query(tower_meta.prefix + '.tower_api', 'john', query_params={}, ) }}" ignore_errors: true register: result @@ -44,7 +47,7 @@ - name: Load user of a specific name without promoting objects set_fact: - users: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}" + users: "{{ lookup(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}" - assert: that: @@ -56,13 +59,13 @@ assert: that: - item['id'] == user_creation_results['results'][0]['id'] - loop: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + loop: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" loop_control: label: "{{ item['id'] }}" - name: Get a page of users as just ids set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}" + users: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}" - name: Assert that user list has 2 integer ids only assert: @@ -72,7 +75,7 @@ - name: Get all users of a system through next attribute set_fact: - users: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}" + users: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}" - assert: that: @@ -80,11 +83,11 @@ - name: Get the ID of the first user created and verify that it is correct assert: - that: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True) }}[0] == {{ user_creation_results['results'][0]['id'] }}" + that: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True) }}[0] == {{ user_creation_results['results'][0]['id'] }}" - name: Try to get an ID of someone who does not exist set_fact: - failed_user_id: "{{ query('awx.awx.tower_api', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}" + failed_user_id: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}" register: result ignore_errors: true @@ -95,7 +98,7 @@ - name: Lookup too many users set_fact: - too_many_user_ids: " {{ query('awx.awx.tower_api', 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}" + too_many_user_ids: " {{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}" register: results ignore_errors: true @@ -107,11 +110,11 @@ - name: Get the settings page assert: that: - - "'CUSTOM_LOGO' in {{ lookup('awx.awx.tower_api', 'settings/ui' ) }}" + - "'CUSTOM_LOGO' in {{ lookup(tower_meta.prefix + '.tower_api', 'settings/ui' ) }}" - name: Get the ping page set_fact: - ping_data: "{{ lookup('awx.awx.tower_api', 'ping' ) }}" + ping_data: "{{ lookup(tower_meta.prefix + '.tower_api', 'ping' ) }}" register: results - assert: diff --git a/awx_collection/tools/roles/template_galaxy/tasks/main.yml b/awx_collection/tools/roles/template_galaxy/tasks/main.yml index d2f7b2929d..414f55b7de 100644 --- a/awx_collection/tools/roles/template_galaxy/tasks/main.yml +++ b/awx_collection/tools/roles/template_galaxy/tasks/main.yml @@ -11,7 +11,7 @@ replace: path: "{{ collection_path }}/plugins/module_utils/tower_api.py" regexp: '^ _COLLECTION_TYPE = "awx"' - replace: ' _COLLECTION_TYPE = "{{ collection_namespace }}"' + replace: ' _COLLECTION_TYPE = "{{ collection_package }}"' - name: Do file content replacements for non-default namespace or package name block: @@ -19,9 +19,12 @@ - name: Change module doc_fragments to support desired namespace and package names replace: path: "{{ item }}" - regexp: '^extends_documentation_fragment: awx.awx.auth' - replace: 'extends_documentation_fragment: {{ collection_namespace }}.{{ collection_package }}.auth' - with_fileglob: "{{ collection_path }}/plugins/modules/tower_*.py" + regexp: '^extends_documentation_fragment: awx.awx.auth([a-zA-Z0-9_]*)$' + replace: 'extends_documentation_fragment: {{ collection_namespace }}.{{ collection_package }}.auth\1' + with_fileglob: + - "{{ collection_path }}/plugins/inventory/*.py" + - "{{ collection_path }}/plugins/lookup/*.py" + - "{{ collection_path }}/plugins/modules/tower_*.py" loop_control: label: "{{ item | basename }}" From a9c16a6c90d50c729cb483590451e888f55609c2 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:18:14 -0400 Subject: [PATCH 10/22] Fixing undefined variabe --- awx_collection/plugins/lookup/tower_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 63e3d30be1..265f4ced07 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -125,7 +125,7 @@ class LookupModule(LookupBase): raise AnsibleError("Unclear response from API: {0}".format(response)) if response['status_code'] != 200: - raise AnsibleError("Failed to query the API: {0}".format(return_data.get('detail', return_data))) + raise AnsibleError("Failed to query the API: {0}".format(response['json'].get('detail', response['json']))) return_data = response['json'] From d793f0bc99491a1d24e3462a9c98eeb4dcddf38c Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:18:53 -0400 Subject: [PATCH 11/22] Making the count check >= instead of just > --- awx_collection/plugins/lookup/tower_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 265f4ced07..4b5de269e6 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -144,7 +144,7 @@ class LookupModule(LookupBase): ) if self.get_option('return_all') and 'results' in return_data: - if return_data['count'] > self.get_option('max_objects'): + 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')) From fdb008fb8cb4aa1c05f7d3dc283e07d725ec03e8 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:21:01 -0400 Subject: [PATCH 12/22] Added note about exception if list view return is > max_objects --- awx_collection/plugins/lookup/tower_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 4b5de269e6..2e30964a2d 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -58,6 +58,7 @@ options: 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 From 1a4bb42ac5f519160d0bc24a9a48e27b72938518 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:45:08 -0400 Subject: [PATCH 13/22] When using return_ids send back a list of strings instead of ints. When we used ints and passed this data into a nother call like: - name: Create a job template with a looked up credential from a folded lookup tower_job_template: name: "{{ job_template_name }}" credentials: >- {{ lookup( 'awx.awx.tower_api', 'credentials', query_params={ 'name' : credential_name }, return_ids=True, expect_one=True, wantlist=True ) }} project: "{{ project_name }}" inventory: Demo Inventory playbook: hello_world.yml job_type: run state: present register: create_jt Ansible would raise this warning: [WARNING]: The value 30 (type int) in a string field was converted to '30' (type string). If this does not look like what you expect, quote the entire value to ensure it does not change. Returning a list of strings prevents that. --- awx_collection/plugins/lookup/tower_api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 2e30964a2d..58ef9c1490 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -52,6 +52,7 @@ options: 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 @@ -160,9 +161,9 @@ class LookupModule(LookupBase): if self.get_option('return_ids'): if 'results' in return_data: - return_data['results'] = [item['id'] for item in return_data['results']] + return_data['results'] = [str(item['id']) for item in return_data['results']] elif 'id' in return_data: - return_data = return_data['id'] + return_data = str(return_data['id']) if self.get_option('return_objects') and 'results' in return_data: return return_data['results'] From 0424370d49b6e238e6ef52db5e2b1766e31967e3 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:48:28 -0400 Subject: [PATCH 14/22] Revamp of the examples --- awx_collection/plugins/lookup/tower_api.py | 39 +++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 58ef9c1490..067604fe79 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -70,12 +70,43 @@ notes: EXAMPLES = """ - name: Load the UI settings - debug: - msg: "{{ query('awx.awx.tower_api', 'settings/ui') }}" + set_fact: + tower_settings: "{{ lookup('awx.awx.tower_api', 'settings/ui') }}" - name: Lookup any users who are admins - debug: - msg: "{{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) }}" + set_fact: + admin_user_list_view: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }, return_objects=False) }}" + +- name: Lookup any users who are admins and get their objects directly + set_fact: + admin_user_object: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true } ) }}" + +- name: Lookup the admin user and fail if there are more than one, and make sure its a list (without this, the response would be an object) + set_fact: + actual_admin_user: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, expect_one=True, wantlist=True) }}" + +- name: Get just the user ID of the admin user + set_fact: + admin_user_id: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, return_ids=True, expect_one=True) }}" + +- name: Create a job template with a looked up credential from a folded lookup + tower_job_template: + name: "{{ job_template_name }}" + credentials: >- + {{ lookup( + 'awx.awx.tower_api', + 'credentials', + query_params={ 'name' : credential_name }, + return_ids=True, + expect_one=True, + wantlist=True + ) }} + project: "{{ project_name }}" + inventory: Demo Inventory + playbook: hello_world.yml + job_type: run + state: present + register: create_jt """ RETURN = """ From dd478af227893e9baeae2b3b47b3ddea3ea3dd23 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:49:49 -0400 Subject: [PATCH 15/22] Overhaul of the testing suite --- .../tower_lookup_api_plugin/tasks/main.yml | 175 ++++++++++++++++-- 1 file changed, 155 insertions(+), 20 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 36325fe9df..e8db9adb44 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -10,6 +10,17 @@ - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}" - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}" - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}" + credential_name: "AWX-Collection-tests-tower_api_lookup-cred1-{{ test_id }}" + job_template_name: "AWX-Collection-tests-tower_api_lookup-jt1-{{ test_id }}" + project_name: "AWX-Collection-tests-tower_api_lookup-proj1-{{ test_id }}" + +- name: Get our collection package + tower_meta: + register: tower_meta + +- name: Generate the name of our plugin + set_fact: + plugin_name: "{{ tower_meta.prefix }}.tower_api" - name: Create all of our users tower_user: @@ -19,13 +30,24 @@ loop: "{{ usernames }}" register: user_creation_results -- tower_meta: - register: tower_meta - - block: + - name: Create our credential + tower_credential: + name: "{{ credential_name }}" + organization: "Default" + credential_type: "Machine" + + - name: Create a Demo Project + tower_project: + name: "{{ project_name }}" + organization: Default + state: present + scm_type: git + scm_url: https://github.com/ansible/ansible-tower-samples.git + - name: Test too many params (failure from validation of terms) set_fact: - junk: "{{ query(tower_meta.prefix + '.tower_api', 'users', 'teams', query_params={}, ) }}" + junk: "{{ query(plugin_name, 'users', 'teams', query_params={}, ) }}" ignore_errors: true register: result @@ -36,7 +58,7 @@ - name: Try to load invalid endpoint set_fact: - junk: "{{ query(tower_meta.prefix + '.tower_api', 'john', query_params={}, ) }}" + junk: "{{ query(plugin_name, 'john', query_params={}, ) }}" ignore_errors: true register: result @@ -47,47 +69,68 @@ - name: Load user of a specific name without promoting objects set_fact: - users: "{{ lookup(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}" + users_list: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=False) }}" - assert: that: - - users['results'] | length() == 1 - - users['count'] == 1 - - users['results'][0]['id'] == user_creation_results['results'][0]['id'] + - users_list['results'] | length() == 1 + - users_list['count'] == 1 + - users_list['results'][0]['id'] == user_creation_results['results'][0]['id'] + + - name: Load user of a specific name with promoting objects + set_fact: + user_objects: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=True, wantlist=True ) }}" + + - assert: + that: + - user_objects | length() == 1 + - users_list['results'][0]['id'] == user_objects[0]['id'] - name: Loop over one user with the loop syntax assert: that: - item['id'] == user_creation_results['results'][0]['id'] - loop: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }) }}" + loop: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, wantlist=True) }}" loop_control: - label: "{{ item['id'] }}" + label: "{{ item.id }}" - name: Get a page of users as just ids set_fact: - users: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}" + users: "{{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 2 }, return_ids=True ) }}" - - name: Assert that user list has 2 integer ids only + - name: Assert that user list has 2 ids only and that they are strings, not ints assert: that: - users | length() == 2 - - user_creation_results['results'][0]['id'] in users + - user_creation_results['results'][0]['id'] not in users + - user_creation_results['results'][0]['id'] | string in users - name: Get all users of a system through next attribute set_fact: - users: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}" + users: "{{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true ) }}" - assert: that: - users | length() >= 3 + - name: Get all of the users created with a max_objects of 1 + set_fact: + users: "{{ lookup(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true, max_objects=1 ) }}" + ignore_errors: True + register: max_user_errors + + - assert: + that: + - max_user_errors is failed + - "'List view at users returned 3 objects, which is more than the maximum allowed by max_objects' in max_user_errors.msg" + - name: Get the ID of the first user created and verify that it is correct assert: - that: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True) }}[0] == {{ user_creation_results['results'][0]['id'] }}" + that: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True, wantlist=True)[0] }} == {{ user_creation_results['results'][0]['id'] }}" - name: Try to get an ID of someone who does not exist set_fact: - failed_user_id: "{{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}" + failed_user_id: "{{ query(plugin_name, 'users', query_params={ 'username': 'john jacob jingleheimer schmidt' }, expect_one=True) }}" register: result ignore_errors: true @@ -98,7 +141,7 @@ - name: Lookup too many users set_fact: - too_many_user_ids: " {{ query(tower_meta.prefix + '.tower_api', 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}" + too_many_user_ids: " {{ query(plugin_name, 'users', query_params={ 'username__endswith': test_id }, expect_one=True) }}" register: results ignore_errors: true @@ -110,11 +153,11 @@ - name: Get the settings page assert: that: - - "'CUSTOM_LOGO' in {{ lookup(tower_meta.prefix + '.tower_api', 'settings/ui' ) }}" + - "'CUSTOM_LOGO' in {{ lookup(plugin_name, 'settings/ui' ) }}" - name: Get the ping page set_fact: - ping_data: "{{ lookup(tower_meta.prefix + '.tower_api', 'ping' ) }}" + ping_data: "{{ lookup(plugin_name, 'ping' ) }}" register: results - assert: @@ -122,7 +165,99 @@ - results is succeeded - "'active_node' in ping_data" + - name: "Make sure that expect_objects fails on an API page" + set_fact: + my_var: "{{ lookup(plugin_name, 'settings/ui', expect_objects=True) }}" + ignore_errors: True + register: results + + - assert: + that: + - results is failed + - "'Did not obtain a list or detail view at settings/ui, and expect_objects or expect_one is set to True' in results.msg" + + # DOCS Example Tests + - name: Load the UI settings + set_fact: + tower_settings: "{{ lookup('awx.awx.tower_api', 'settings/ui') }}" + + - assert: + that: + - "'CUSTOM_LOGO' in tower_settings" + + - name: Lookup any users who are admins + set_fact: + admin_user_list_view: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }, return_objects=False) }}" + + - assert: + that: + - "'count' in admin_user_list_view" + + - name: Lookup any users who are admins and get their objects directly + set_fact: + admin_user_object: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true } ) }}" + + - assert: + that: + - "'count' not in admin_user_object" + + - name: Lookup the admin user and fail if there are more than one, and make sure its a list (without this, the response would be an object) + set_fact: + actual_admin_user: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, expect_one=True, wantlist=True) }}" + + - assert: + that: + - actual_admin_user | length() == 1 + + - name: Get just the user ID of the admin user + set_fact: + admin_user_id: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, return_ids=True, expect_one=True) }}" + + - assert: + that: + - admin_user_id | int() >= 1 + + - name: Create a job template with a looked up credential from a folded lookup + tower_job_template: + name: "{{ job_template_name }}" + credentials: >- + {{ lookup( + 'awx.awx.tower_api', + 'credentials', + query_params={ 'name' : credential_name }, + return_ids=True, + expect_one=True, + wantlist=True + ) }} + project: "{{ project_name }}" + inventory: Demo Inventory + playbook: hello_world.yml + job_type: run + state: present + register: create_jt + + - assert: + that: + - create_jt is changed + always: + - name: Cleanup job template + tower_job_template: + name: "{{ job_template_name }}" + state: absent + + - name: Delete our credential + tower_credential: + name: "{{ credential_name }}" + credential_type: Machine + state: absent + + - name: Cleanup our project + tower_project: + name: "{{ project_name }}" + organization: Default + state: absent + - name: Cleanup users tower_user: username: "{{ item }}" From 381e9d2901a1603de260e30604b5b70e7ce983cc Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 09:55:55 -0400 Subject: [PATCH 16/22] Reverting commit 81b192c3b0520be0897f95e2630edab295e6bf24, we should fail only if we are > max_objects --- awx_collection/plugins/lookup/tower_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 067604fe79..25b912fce7 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -177,7 +177,7 @@ class LookupModule(LookupBase): ) if self.get_option('return_all') and 'results' in return_data: - if return_data['count'] >= self.get_option('max_objects'): + 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')) From afbdeb5ff82fd1c8bc5682bcfdf1569758cd3109 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 11:08:37 -0400 Subject: [PATCH 17/22] Fixing linting errors --- .../targets/tower_lookup_api_plugin/tasks/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index e8db9adb44..b98c78b3c9 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -116,7 +116,7 @@ - name: Get all of the users created with a max_objects of 1 set_fact: users: "{{ lookup(plugin_name, 'users', query_params={ 'username__endswith': test_id, 'page_size': 1 }, return_all=true, max_objects=1 ) }}" - ignore_errors: True + ignore_errors: true register: max_user_errors - assert: @@ -168,7 +168,7 @@ - name: "Make sure that expect_objects fails on an API page" set_fact: my_var: "{{ lookup(plugin_name, 'settings/ui', expect_objects=True) }}" - ignore_errors: True + ignore_errors: true register: results - assert: @@ -235,7 +235,7 @@ job_type: run state: present register: create_jt - + - assert: that: - create_jt is changed From 6638d6c2bb6323ce15ed8644181e1603bb56376f Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Mon, 6 Jul 2020 17:30:02 -0400 Subject: [PATCH 18/22] Update examples --- awx_collection/plugins/lookup/tower_api.py | 51 ++++---- .../tower_lookup_api_plugin/tasks/main.yml | 115 +++++++----------- 2 files changed, 69 insertions(+), 97 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index 25b912fce7..f9b6d591cf 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -65,7 +65,6 @@ options: notes: - If the query is not filtered properly this can cause a performance impact. - - In addition, the built in threshold is 10,000 items; if the query returns more an exception will be thrown. """ EXAMPLES = """ @@ -73,40 +72,34 @@ EXAMPLES = """ set_fact: tower_settings: "{{ lookup('awx.awx.tower_api', 'settings/ui') }}" -- name: Lookup any users who are admins - set_fact: - admin_user_list_view: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }, return_objects=False) }}" +- name: Report the usernames of all users with admin privs + debug: + msg: "Admin users: {{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}" - name: Lookup any users who are admins and get their objects directly set_fact: admin_user_object: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true } ) }}" -- name: Lookup the admin user and fail if there are more than one, and make sure its a list (without this, the response would be an object) - set_fact: - actual_admin_user: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, expect_one=True, wantlist=True) }}" +- name: Make sure user 'john' is an org admin of the default org if the user exists + tower_role: + organization: Default + role: admin + user: john + state: absent + register: tower_role_revoke + when: "lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'john' }) | length == 1" -- name: Get just the user ID of the admin user - set_fact: - admin_user_id: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, return_ids=True, expect_one=True) }}" - -- name: Create a job template with a looked up credential from a folded lookup - tower_job_template: - name: "{{ job_template_name }}" - credentials: >- - {{ lookup( - 'awx.awx.tower_api', - 'credentials', - query_params={ 'name' : credential_name }, - return_ids=True, - expect_one=True, - wantlist=True - ) }} - project: "{{ project_name }}" - inventory: Demo Inventory - playbook: hello_world.yml - job_type: run - state: present - register: create_jt +- name: Create an inventory group with all 'foo' hosts + tower_group: + name: "Foo Group" + inventory: "Demo Inventory" + hosts: >- + {{ query( + 'awx.awx.tower_api', + 'hosts', + query_params={ 'name__startswith' : 'foo', }, + ) | map(attribute='name') | list }} + register: group_creation """ RETURN = """ diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index b98c78b3c9..0f2bef3cce 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -10,9 +10,10 @@ - "AWX-Collection-tests-tower_api_lookup-user1-{{ test_id }}" - "AWX-Collection-tests-tower_api_lookup-user2-{{ test_id }}" - "AWX-Collection-tests-tower_api_lookup-user3-{{ test_id }}" - credential_name: "AWX-Collection-tests-tower_api_lookup-cred1-{{ test_id }}" - job_template_name: "AWX-Collection-tests-tower_api_lookup-jt1-{{ test_id }}" - project_name: "AWX-Collection-tests-tower_api_lookup-proj1-{{ test_id }}" + hosts: + - "AWX-Collection-tests-tower_api_lookup-host1-{{ test_id }}" + - "AWX-Collection-tests-tower_api_lookup-host2-{{ test_id }}" + group_name: "AWX-Collection-tests-tower_api_lookup-group1-{{ test_id }}" - name: Get our collection package tower_meta: @@ -31,19 +32,11 @@ register: user_creation_results - block: - - name: Create our credential - tower_credential: - name: "{{ credential_name }}" - organization: "Default" - credential_type: "Machine" - - - name: Create a Demo Project - tower_project: - name: "{{ project_name }}" - organization: Default - state: present - scm_type: git - scm_url: https://github.com/ansible/ansible-tower-samples.git + - name: Create our hosts + tower_host: + name: "{{ item }}" + inventory: "Demo Inventory" + loop: "{{ hosts }}" - name: Test too many params (failure from validation of terms) set_fact: @@ -79,7 +72,7 @@ - name: Load user of a specific name with promoting objects set_fact: - user_objects: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=True, wantlist=True ) }}" + user_objects: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_objects=True ) }}" - assert: that: @@ -90,7 +83,7 @@ assert: that: - item['id'] == user_creation_results['results'][0]['id'] - loop: "{{ lookup(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, wantlist=True) }}" + loop: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] } ) }}" loop_control: label: "{{ item.id }}" @@ -126,7 +119,7 @@ - name: Get the ID of the first user created and verify that it is correct assert: - that: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True, wantlist=True)[0] }} == {{ user_creation_results['results'][0]['id'] }}" + that: "{{ query(plugin_name, 'users', query_params={ 'username' : user_creation_results['results'][0]['item'] }, return_ids=True)[0] }} == {{ user_creation_results['results'][0]['id'] }}" - name: Try to get an ID of someone who does not exist set_fact: @@ -185,13 +178,14 @@ that: - "'CUSTOM_LOGO' in tower_settings" - - name: Lookup any users who are admins - set_fact: - admin_user_list_view: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }, return_objects=False) }}" + - name: Display the usernames of all admin users + debug: + msg: "Admin users: {{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}" + register: results - assert: that: - - "'count' in admin_user_list_view" + - "'admin' in results.msg" - name: Lookup any users who are admins and get their objects directly set_fact: @@ -201,62 +195,47 @@ that: - "'count' not in admin_user_object" - - name: Lookup the admin user and fail if there are more than one, and make sure its a list (without this, the response would be an object) - set_fact: - actual_admin_user: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, expect_one=True, wantlist=True) }}" + - name: Make sure user 'john' is an org admin of the default org if the user exists + tower_role: + organization: Default + role: admin + user: "{{ usernames[0] }}" + state: absent + register: tower_role_revoke + when: "query('awx.awx.tower_api', 'users', query_params={ 'username': 'DNE_TESTING' }) | length == 1" - assert: that: - - actual_admin_user | length() == 1 + - tower_role_revoke is skipped - - name: Get just the user ID of the admin user - set_fact: - admin_user_id: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'admin' }, return_ids=True, expect_one=True) }}" + - name: Create an inventory group with all 'foo' hosts + tower_group: + name: "{{ group_name }}" + inventory: "Demo Inventory" + hosts: >- + {{ query( + 'awx.awx.tower_api', + 'hosts', + query_params={ 'name__endswith' : test_id, }, + ) | map(attribute='name') | list }} + register: group_creation - assert: - that: - - admin_user_id | int() >= 1 - - - name: Create a job template with a looked up credential from a folded lookup - tower_job_template: - name: "{{ job_template_name }}" - credentials: >- - {{ lookup( - 'awx.awx.tower_api', - 'credentials', - query_params={ 'name' : credential_name }, - return_ids=True, - expect_one=True, - wantlist=True - ) }} - project: "{{ project_name }}" - inventory: Demo Inventory - playbook: hello_world.yml - job_type: run - state: present - register: create_jt - - - assert: - that: - - create_jt is changed + that: group_creation is changed always: - - name: Cleanup job template - tower_job_template: - name: "{{ job_template_name }}" + - name: Cleanup group + tower_group: + name: "{{ group_name }}" + inventory: "Demo Inventory" state: absent - - name: Delete our credential - tower_credential: - name: "{{ credential_name }}" - credential_type: Machine - state: absent - - - name: Cleanup our project - tower_project: - name: "{{ project_name }}" - organization: Default + - name: Cleanup hosts + tower_host: + name: "{{ item }}" + inventory: "Demo Inventory" state: absent + loop: "{{ hosts }}" - name: Cleanup users tower_user: From 3784f9515e3acbf846c0d25d8bf4b212b37f7947 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Tue, 7 Jul 2020 12:10:54 -0400 Subject: [PATCH 19/22] More refinement of examples --- awx_collection/plugins/lookup/tower_api.py | 11 ++++++----- .../targets/tower_lookup_api_plugin/tasks/main.yml | 13 ++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/awx_collection/plugins/lookup/tower_api.py b/awx_collection/plugins/lookup/tower_api.py index f9b6d591cf..9829507125 100644 --- a/awx_collection/plugins/lookup/tower_api.py +++ b/awx_collection/plugins/lookup/tower_api.py @@ -76,17 +76,18 @@ EXAMPLES = """ debug: msg: "Admin users: {{ query('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true }) | map(attribute='username') | join(', ') }}" -- name: Lookup any users who are admins and get their objects directly - set_fact: - admin_user_object: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true } ) }}" +- 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.tower_api', 'organizations') }}" + loop_control: + label: "{{ item['name'] }}" - name: Make sure user 'john' is an org admin of the default org if the user exists tower_role: organization: Default role: admin user: john - state: absent - register: tower_role_revoke when: "lookup('awx.awx.tower_api', 'users', query_params={ 'username': 'john' }) | length == 1" - name: Create an inventory group with all 'foo' hosts diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index 0f2bef3cce..e4415e24d4 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -187,13 +187,12 @@ that: - "'admin' in results.msg" - - name: Lookup any users who are admins and get their objects directly - set_fact: - admin_user_object: "{{ lookup('awx.awx.tower_api', 'users', query_params={ 'is_superuser': true } ) }}" - - - assert: - that: - - "'count' not in admin_user_object" + - 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.tower_api', 'organizations') }}" + loop_control: + label: "{{ item['name'] }}" - name: Make sure user 'john' is an org admin of the default org if the user exists tower_role: From 5a6c7a57884af0301af87ed6d1bdad937736a91a Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Wed, 8 Jul 2020 13:51:13 -0400 Subject: [PATCH 20/22] Fixing copyrights and linting issues --- awx_collection/plugins/doc_fragments/auth_plugin.py | 2 +- awx_collection/plugins/modules/tower_meta.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/awx_collection/plugins/doc_fragments/auth_plugin.py b/awx_collection/plugins/doc_fragments/auth_plugin.py index 25c0b9e8e2..527054ed27 100644 --- a/awx_collection/plugins/doc_fragments/auth_plugin.py +++ b/awx_collection/plugins/doc_fragments/auth_plugin.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright: (c) 2017, Wayne Witzel III +# Copyright: (c) 2020, Ansible by Red Hat, Inc # 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) diff --git a/awx_collection/plugins/modules/tower_meta.py b/awx_collection/plugins/modules/tower_meta.py index 34b4453c18..6d5c801ade 100644 --- a/awx_collection/plugins/modules/tower_meta.py +++ b/awx_collection/plugins/modules/tower_meta.py @@ -1,6 +1,9 @@ #!/usr/bin/python # coding: utf-8 -*- +# (c) 2020, Ansible by Red Hat, Inc +# 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 @@ -9,7 +12,6 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} - DOCUMENTATION = ''' --- module: tower_meta @@ -28,18 +30,22 @@ prefix: description: Collection namespace and name in the namespace.name format returned: success sample: awx.awx + type: str name: description: Collection name returned: success sample: awx + type: str namespace: description: Collection namespace returned: success sample: awx + type: str version: description: Version of the collection returned: success sample: 0.0.1-devel + type: str ''' From 0be7d9b21afaceaa16dc7a1478482e0b187b7dad Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 9 Jul 2020 08:40:11 -0400 Subject: [PATCH 21/22] Removing redundant check --- .../targets/tower_lookup_api_plugin/tasks/main.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml index e4415e24d4..9f29b0d9ab 100644 --- a/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_lookup_api_plugin/tasks/main.yml @@ -143,11 +143,6 @@ - results is failed - "'Expected one object from endpoint users, but obtained 3' in results['msg']" - - name: Get the settings page - assert: - that: - - "'CUSTOM_LOGO' in {{ lookup(plugin_name, 'settings/ui' ) }}" - - name: Get the ping page set_fact: ping_data: "{{ lookup(plugin_name, 'ping' ) }}" From 222e58dd34552b928a93a7abcbb510117a3745bf Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 9 Jul 2020 09:09:47 -0400 Subject: [PATCH 22/22] Adding lookup to rrule plugin test logic for new tooling This change is provided with the tower_api lookup plugin because the require tower_meta module is part of this commit --- .../targets/tower_schedule_rrule/tasks/main.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_schedule_rrule/tasks/main.yml b/awx_collection/tests/integration/targets/tower_schedule_rrule/tasks/main.yml index a2468a697f..837821bac2 100644 --- a/awx_collection/tests/integration/targets/tower_schedule_rrule/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_schedule_rrule/tasks/main.yml @@ -1,7 +1,15 @@ --- +- name: Get our collection package + tower_meta: + register: tower_meta + +- name: Generate the name of our plugin + set_fact: + plugin_name: "{{ tower_meta.prefix }}.tower_schedule_rrule" + - name: Test too many params (failure from validation of terms) debug: - msg: "{{ query('awx.awx.tower_schedule_rrule', 'none', 'weekly', start_date='2020-4-16 03:45:07') }}" + msg: "{{ query(plugin_name, 'none', 'weekly', start_date='2020-4-16 03:45:07') }}" ignore_errors: true register: result @@ -12,7 +20,7 @@ - name: Test invalid frequency (failure from validation of term) debug: - msg: "{{ query('awx.awx.tower_schedule_rrule', 'john', start_date='2020-4-16 03:45:07') }}" + msg: "{{ query(plugin_name, 'john', start_date='2020-4-16 03:45:07') }}" ignore_errors: true register: result @@ -23,7 +31,7 @@ - name: Test an invalid start date (generic failure case from get_rrule) debug: - msg: "{{ query('awx.awx.tower_schedule_rrule', 'none', start_date='invalid') }}" + msg: "{{ query(plugin_name, 'none', start_date='invalid') }}" ignore_errors: true register: result @@ -34,7 +42,7 @@ - name: Test end_on as count (generic success case) debug: - msg: "{{ query('awx.awx.tower_schedule_rrule', 'minute', start_date='2020-4-16 03:45:07', end_on='2') }}" + msg: "{{ query(plugin_name, 'minute', start_date='2020-4-16 03:45:07', end_on='2') }}" register: result - assert: