From baf3b617cbc940f12aec8dfd59e85a1b60a44e55 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 24 Sep 2020 10:20:00 -0400 Subject: [PATCH 1/4] Initial commit of ad hoc module --- .../plugins/modules/tower_ad_hoc_command.py | 187 ++++++++++++++++++ .../modules/tower_ad_hoc_command_cancel.py | 130 ++++++++++++ .../modules/tower_ad_hoc_command_wait.py | 127 ++++++++++++ awx_collection/test/awx/test_ad_hoc_wait.py | 55 ++++++ awx_collection/test/awx/test_completeness.py | 4 +- .../tower_ad_hoc_command/tasks/main.yml | 37 ++++ .../tasks/main.yml | 63 ++++++ .../tower_ad_hoc_command_wait/tasks/main.yml | 87 ++++++++ awx_collection/tools/vars/resolution.yml | 1 + 9 files changed, 690 insertions(+), 1 deletion(-) create mode 100644 awx_collection/plugins/modules/tower_ad_hoc_command.py create mode 100644 awx_collection/plugins/modules/tower_ad_hoc_command_cancel.py create mode 100644 awx_collection/plugins/modules/tower_ad_hoc_command_wait.py create mode 100644 awx_collection/test/awx/test_ad_hoc_wait.py create mode 100644 awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml create mode 100644 awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml diff --git a/awx_collection/plugins/modules/tower_ad_hoc_command.py b/awx_collection/plugins/modules/tower_ad_hoc_command.py new file mode 100644 index 0000000000..d952f954cf --- /dev/null +++ b/awx_collection/plugins/modules/tower_ad_hoc_command.py @@ -0,0 +1,187 @@ +#!/usr/bin/python +# coding: utf-8 -*- + + +# (c) 2020, John Westcott IV +# 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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: tower_ad_hoc_command +author: "John Westcott IV (@john-westcott-iv)" +version_added: "4.0" +short_description: create, update, or destroy Ansible Tower ad hoc commands. +description: + - Create, update, or destroy Ansible Tower ad hoc commands. See + U(https://www.ansible.com/tower) for an overview. +options: + job_type: + description: + - Job_type to use for the ad hoc command. + type: str + choices: [ 'run', 'check' ] + inventory: + description: + - Inventory to use for the ad hoc command. + required: True + type: str + limit: + description: + - Limit to use for the ad hoc command. + type: str + credential: + description: + - Credential to use for ad hoc command. + required: True + type: str + module_name: + description: + - The Ansible module to execute. + required: True + type: str + module_args: + description: + - The arguments to pass to the module. + type: str + default: "" + forks: + description: + - The number of forks to use for this ad hoc execution. + type: int + verbosity: + description: + - Verbosity level for this ad hoc command run + type: int + choices: [ 0, 1, 2, 3, 4, 5 ] + extra_vars: + description: + - Extra variables to use for the ad hoc command.. + type: dict + become_enabled: + description: + - If the become flag should be set. + type: bool + diff_mode: + description: + - Show the changes made by Ansible tasks where supported + type: bool + wait: + description: + - Wait for the command to complete. + default: False + type: bool + interval: + description: + - The interval to request an update from Tower. + default: 1 + type: float + timeout: + description: + - If waiting for the command to complete this will abort after this + amount of seconds + type: int +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +''' + +RETURN = ''' +id: + description: id of the newly launched command + returned: success + type: int + sample: 86 +status: + description: status of newly launched command + returned: success + type: str + sample: pending +''' + +from ..module_utils.tower_api import TowerAPIModule + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + job_type=dict(choices=['run', 'check']), + inventory=dict(required=True), + limit=dict(), + credential=dict(required=True), + module_name=dict(required=True), + module_args=dict(default=""), + forks=dict(type='int'), + verbosity=dict(type='int', choices=['0', '1', '2', '3', '4', '5']), + extra_vars=dict(type='dict'), + become_enabled=dict(type='bool'), + diff_mode=dict(type='bool'), + wait=dict(default=False, type='bool'), + interval=dict(default=1.0, type='float'), + timeout=dict(default=None, type='int'), + ) + + # Create a module for ourselves + module = TowerAPIModule(argument_spec=argument_spec) + + # Extract our parameters + inventory = module.params.get('inventory') + credential = module.params.get('credential') + module_name = module.params.get('module_name') + module_args = module.params.get('module_args') + + wait = module.params.get('wait') + interval = module.params.get('interval') + timeout = module.params.get('timeout') + + # Create a datastructure to pass into our command launch + post_data = { + 'module_name': module_name, + 'module_args': module_args, + } + for arg in ['job_type', 'limit', 'forks', 'verbosity', 'extra_vars', 'become_enabled', 'diff_mode']: + if module.params.get(arg): + post_data[arg] = module.params.get(arg) + + # Attempt to look up the related items the user specified (these will fail the module if not found) + post_data['inventory'] = module.resolve_name_to_id('inventories', inventory) + post_data['credential'] = module.resolve_name_to_id('credentials', credential) + + # Launch the ad hoc command + results = module.post_endpoint('ad_hoc_commands', **{'data': post_data}) + + if results['status_code'] != 201: + module.fail_json(msg="Failed to launch command, see response for details", **{'response': results}) + + if not wait: + module.exit_json(**{ + 'changed': True, + 'id': results['json']['id'], + 'status': results['json']['status'], + }) + + # Invoke wait function + results = module.wait_on_url( + url=results['json']['url'], + object_name=module_name, + object_type='Ad Hoc Command', + timeout=timeout, interval=interval + ) + + module.exit_json(**{ + 'changed': True, + 'id': results['json']['id'], + 'status': results['json']['status'], + }) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/plugins/modules/tower_ad_hoc_command_cancel.py b/awx_collection/plugins/modules/tower_ad_hoc_command_cancel.py new file mode 100644 index 0000000000..4e88b22f7a --- /dev/null +++ b/awx_collection/plugins/modules/tower_ad_hoc_command_cancel.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: tower_ad_hoc_command_cancel +author: "John Westcott IV (@john-westcott-iv)" +short_description: Cancel an Ansible Tower Ad Hoc Command. +description: + - Cancel Ansible Tower ad hoc command. See + U(https://www.ansible.com/tower) for an overview. +options: + command_id: + description: + - ID of the command to cancel + required: True + type: int + fail_if_not_running: + description: + - Fail loudly if the I(command_id) can not be canceled + default: False + type: bool + interval: + description: + - The interval in seconds, to request an update from Tower. + required: False + default: 1 + type: float + timeout: + description: + - Maximum time in seconds to wait for a job to finish. + - Not specifying means the task will wait until Tower cancels the command. + type: int +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +- name: Cancel command + tower_ad_hoc_command_cancel: + command_id: command.id +''' + +RETURN = ''' +id: + description: command id requesting to cancel + returned: success + type: int + sample: 94 +''' + + +import time + +from ..module_utils.tower_api import TowerAPIModule + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + command_id=dict(type='int', required=True), + fail_if_not_running=dict(type='bool', default=False), + interval=dict(type='float', default=1.0), + timeout=dict(type='int', default=0), + ) + + # Create a module for ourselves + module = TowerAPIModule(argument_spec=argument_spec) + + # Extract our parameters + command_id = module.params.get('command_id') + fail_if_not_running = module.params.get('fail_if_not_running') + interval = module.params.get('interval') + timeout = module.params.get('timeout') + + # Attempt to look up the command based on the provided name + command = module.get_one('ad_hoc_commands', **{ + 'data': { + 'id': command_id, + } + }) + + if command is None: + module.fail_json(msg="Unable to find command with id {0}".format(command_id)) + + cancel_page = module.get_endpoint(command['related']['cancel']) + if 'json' not in cancel_page or 'can_cancel' not in cancel_page['json']: + module.fail_json(msg="Failed to cancel command, got unexpected response from tower", **{'response': cancel_page}) + + if not cancel_page['json']['can_cancel']: + if fail_if_not_running: + module.fail_json(msg="Ad Hoc Command is not running") + else: + module.exit_json(**{'changed': False}) + + results = module.post_endpoint(command['related']['cancel'], **{'data': {}}) + + if results['status_code'] != 202: + module.fail_json(msg="Failed to cancel command, see response for details", **{'response': results}) + + result = module.get_endpoint(command['related']['cancel']) + start = time.time() + while result['json']['can_cancel']: + # If we are past our time out fail with a message + if timeout and timeout < time.time() - start: + # Account for Legacy messages + module.json_output['msg'] = 'Monitoring of ad hoc command aborted due to timeout' + module.fail_json(**module.json_output) + + # Put the process to sleep for our interval + time.sleep(interval) + + result = module.get_endpoint(command['related']['cancel']) + + module.exit_json(**{'changed': True}) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py b/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py new file mode 100644 index 0000000000..70c15c47d5 --- /dev/null +++ b/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (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 + + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + + +DOCUMENTATION = ''' +--- +module: tower_ad_hoc_command_wait +author: "John Westcott IV (@john-westcott-iv)" +short_description: Wait for Ansible Tower Ad Hoc Command to finish. +description: + - Wait for Ansible Tower ad hoc command to finish and report success or failure. See + U(https://www.ansible.com/tower) for an overview. +options: + command_id: + description: + - ID of the ad hoc command to monitor. + required: True + type: int + interval: + description: + - The interval in sections, to request an update from Tower. + required: False + default: 1 + type: float + timeout: + description: + - Maximum time in seconds to wait for a ad hoc command to finish. + type: int +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +- name: Launch an ad hoc command + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + wait: False + register: command + +- name: Wait for ad joc command max 120s + tower_ad_hoc_command_wait: + command_id: "{{ command.id }}" + timeout: 120 +''' + +RETURN = ''' +id: + description: Ad hoc command id that is being waited on + returned: success + type: int + sample: 99 +elapsed: + description: total time in seconds the command took to run + returned: success + type: float + sample: 10.879 +started: + description: timestamp of when the command started running + returned: success + type: str + sample: "2017-03-01T17:03:53.200234Z" +finished: + description: timestamp of when the command finished running + returned: success + type: str + sample: "2017-03-01T17:04:04.078782Z" +status: + description: current status of command + returned: success + type: str + sample: successful +''' + + +from ..module_utils.tower_api import TowerAPIModule + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + command_id=dict(type='int', required=True), + timeout=dict(type='int'), + interval=dict(type='float', default=1), + ) + + # Create a module for ourselves + module = TowerAPIModule(argument_spec=argument_spec) + + # Extract our parameters + command_id = module.params.get('command_id') + timeout = module.params.get('timeout') + interval = module.params.get('interval') + + # Attempt to look up command based on the provided id + command = module.get_one('ad_hoc_commands', **{ + 'data': { + 'id': command_id, + } + }) + + if command is None: + module.fail_json(msg='Unable to wait on ad hoc command {0}; that ID does not exist in Tower.'.format(command_id)) + + # Invoke wait function + module.wait_on_url( + url=command['url'], + object_name=command_id, + object_type='ad hoc command', + timeout=timeout, interval=interval + ) + + module.exit_json(**module.json_output) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/test/awx/test_ad_hoc_wait.py b/awx_collection/test/awx/test_ad_hoc_wait.py new file mode 100644 index 0000000000..976e3d0e80 --- /dev/null +++ b/awx_collection/test/awx/test_ad_hoc_wait.py @@ -0,0 +1,55 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +from django.utils.timezone import now + +from awx.main.models.ad_hoc_commands import AdHocCommand + + +@pytest.mark.django_db +def test_ad_hoc_command_wait_successful(run_module, admin_user): + command = AdHocCommand.objects.create(status='successful', started=now(), finished=now()) + result = run_module('tower_ad_hoc_command_wait', dict( + command_id=command.id + ), admin_user) + result.pop('invocation', None) + assert result.pop('finished', '')[:10] == str(command.finished)[:10] + assert result.pop('started', '')[:10] == str(command.started)[:10] + assert result == { + "status": "successful", + "changed": False, + "elapsed": str(command.elapsed), + "id": command.id + } + + +@pytest.mark.django_db +def test_ad_hoc_command_wait_failed(run_module, admin_user): + command = AdHocCommand.objects.create(status='failed', started=now(), finished=now()) + result = run_module('tower_ad_hoc_command_wait', dict( + command_id=command.id + ), admin_user) + result.pop('invocation', None) + assert result.pop('finished', '')[:10] == str(command.finished)[:10] + assert result.pop('started', '')[:10] == str(command.started)[:10] + assert result == { + "status": "failed", + "failed": True, + "changed": False, + "elapsed": str(command.elapsed), + "id": command.id, + "msg": "The ad hoc command - 1, failed" + } + + +@pytest.mark.django_db +def test_ad_hoc_command_wait_not_found(run_module, admin_user): + result = run_module('tower_ad_hoc_command_wait', dict( + command_id=42 + ), admin_user) + result.pop('invocation', None) + assert result == { + "failed": True, + "msg": "Unable to wait on ad hoc command 42; that ID does not exist in Tower." + } diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index bf8e4f835a..4dd06a31ac 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -25,7 +25,7 @@ no_module_for_endpoint = [] no_endpoint_for_module = [ 'tower_import', 'tower_meta', 'tower_export', 'tower_inventory_source_update', 'tower_job_launch', 'tower_job_wait', 'tower_job_list', 'tower_license', 'tower_ping', 'tower_receive', 'tower_send', 'tower_workflow_launch', - 'tower_job_cancel', 'tower_workflow_template', + 'tower_job_cancel', 'tower_workflow_template', 'tower_ad_hoc_command_wait', 'tower_ad_hoc_command_cancel', ] # Global module parameters we can ignore @@ -48,6 +48,8 @@ no_api_parameter_ok = { 'tower_workflow_job_template_node': ['organization'], # Survey is how we handle associations 'tower_workflow_job_template': ['survey'], + # ad hoc commands support interval and timeout since its more like tower_job_launc + 'tower_ad_hoc_command': ['interval', 'timeout', 'wait'], } # When this tool was created we were not feature complete. Adding something in here indicates a module diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml new file mode 100644 index 0000000000..af9045ef88 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml @@ -0,0 +1,37 @@ +--- +#- 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 names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Launch an Ad Hoc Command waiting for it to finish + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + module_name: "command" + module_args: "echo I<3 Ansible" + wait: True + register: result + +- assert: + that: + - "result is changed" + - "result.status == 'successful'" + +- name: Check module fails with correct msg + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + module_name: "Does not exist" + register: result + ignore_errors: true + +- assert: + that: + - "result is failed" + - "result is not changed" + - "'Does not exist' in result.response['json']['module_name'][0]" diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml new file mode 100644 index 0000000000..e47d25c3cd --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml @@ -0,0 +1,63 @@ +--- +#- 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 names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Launch an Ad Hoc Command + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + module_name: "command" + module_args: "sleep 10" + register: command + +- assert: + that: + - "command is changed" + +- name: Timeout waiting for the command to cancel + tower_ad_hoc_command_cancel: + command_id: "{{ command.id }}" + timeout: -1 + register: results + ignore_errors: true + +- assert: + that: + - results is failed + - "results['msg'] == 'Monitoring of ad hoc command aborted due to timeout'" + +- name: Cancel the command + tower_ad_hoc_command_cancel: + command_id: "{{ command.id }}" + register: results + +- assert: + that: + - results is changed + +- name: Cancel an already canceled command (assert failure) + tower_ad_hoc_command_cancel: + command_id: "{{ command.id }}" + fail_if_not_running: true + register: results + ignore_errors: true + +- assert: + that: + - results is failed + +- name: Check module fails with correct msg + tower_ad_hoc_command_cancel: + command_id: 9999999999 + register: result + ignore_errors: true + +- assert: + that: + - "result.msg == 'Unable to find command with id 9999999999'" diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml new file mode 100644 index 0000000000..d6750e2c9a --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml @@ -0,0 +1,87 @@ +--- +#- 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 names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: Check module fails with correct msg + tower_ad_hoc_command_wait: + command_id: "99999999" + register: result + ignore_errors: true + +- assert: + that: + - result is failed + - "result.msg == 'Unable to wait on ad hoc command 99999999; that ID does not exist in Tower.'" + +- name: Launch command module with sleep 10 + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + module_name: "command" + module_args: "sleep 5" + register: command + +- assert: + that: + - command is changed + +- name: Wait for the Job to finish + tower_ad_hoc_command_wait: + command_id: "{{ command.id }}" + register: wait_results + +# Make sure it worked and that we have some data in our results +- assert: + that: + - wait_results is successful + - "'elapsed' in wait_results" + - "'id' in wait_results" + +- name: Launch a long running command + tower_ad_hoc_command: + inventory: "Demo Inventory" + credential: "Demo Credential" + module_name: "command" + module_args: "sleep 100" + register: command + +- assert: + that: + - command is changed + +- name: Timeout waiting for the command to complete + tower_ad_hoc_command_wait: + command_id: "{{ command.id }}" + timeout: 1 + ignore_errors: true + register: wait_results + +# Make sure that we failed and that we have some data in our results +- assert: + that: + - "wait_results.msg == 'Monitoring aborted due to timeout' or 'Timeout waiting for command to finish.'" + - "'id' in wait_results" + +- name: Async cancel the long running command + tower_ad_hoc_command_cancel: + command_id: "{{ command.id }}" + async: 3600 + poll: 0 + +- name: Wait for the command to exit on cancel + tower_ad_hoc_command_wait: + command_id: "{{ command.id }}" + register: wait_results + ignore_errors: true + +- assert: + that: + - wait_results is failed + - 'wait_results.status == "canceled"' + - "wait_results.msg == 'The ad hoc command - {{ command.id }}, failed'" diff --git a/awx_collection/tools/vars/resolution.yml b/awx_collection/tools/vars/resolution.yml index fd6ec096cb..2beda0ff23 100644 --- a/awx_collection/tools/vars/resolution.yml +++ b/awx_collection/tools/vars/resolution.yml @@ -4,3 +4,4 @@ name_to_id_endpoint_resolution: project: projects inventory: inventories organization: organizations + credential: credentials From 5b104822563a495e073c3aa5f25633e7cc23b3d6 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 24 Sep 2020 10:59:40 -0400 Subject: [PATCH 2/4] Fixing linting --- .../targets/tower_ad_hoc_command/tasks/main.yml | 16 ++++++++-------- .../tower_ad_hoc_command_cancel/tasks/main.yml | 14 +++++++------- .../tower_ad_hoc_command_wait/tasks/main.yml | 14 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml index af9045ef88..9fa3d47ad2 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml @@ -1,12 +1,12 @@ --- -#- 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +# - name: Generate names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - name: Launch an Ad Hoc Command waiting for it to finish tower_ad_hoc_command: @@ -14,7 +14,7 @@ credential: "Demo Credential" module_name: "command" module_args: "echo I<3 Ansible" - wait: True + wait: true register: result - assert: diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml index e47d25c3cd..0cbc583ac3 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml @@ -1,12 +1,12 @@ --- -#- 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +# - name: Generate names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - name: Launch an Ad Hoc Command tower_ad_hoc_command: diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml index d6750e2c9a..babaca0bfc 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml @@ -1,12 +1,12 @@ --- -#- 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +# - name: Generate names +# set_fact: +# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" - name: Check module fails with correct msg tower_ad_hoc_command_wait: From 842e490ba66cb40205a4bef718072bb6aa607854 Mon Sep 17 00:00:00 2001 From: John Westcott IV Date: Thu, 24 Sep 2020 11:48:50 -0400 Subject: [PATCH 3/4] Removing needs devel ad hoc entry --- awx_collection/test/awx/test_completeness.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index 4dd06a31ac..3626c42239 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -56,7 +56,7 @@ no_api_parameter_ok = { # that needs to be developed. If the module is found on the file system it will auto-detect that the # work is being done and will bypass this check. At some point this module should be removed from this list. needs_development = [ - 'tower_ad_hoc_command', 'tower_inventory_script', 'tower_workflow_approval' + 'tower_inventory_script', 'tower_workflow_approval' ] needs_param_development = { 'tower_host': ['instance_id'], From b34c1f4c791d9f1dcc6b532402ee953ae2a975ed Mon Sep 17 00:00:00 2001 From: beeankha Date: Fri, 25 Sep 2020 11:10:46 -0400 Subject: [PATCH 4/4] Update integration tests --- .../modules/tower_ad_hoc_command_wait.py | 2 +- .../tower_ad_hoc_command/tasks/main.yml | 68 ++++++++++++++---- .../tasks/main.yml | 67 ++++++++++++++--- .../tower_ad_hoc_command_wait/tasks/main.yml | 72 +++++++++++++++---- 4 files changed, 171 insertions(+), 38 deletions(-) diff --git a/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py b/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py index 70c15c47d5..7d1cc19418 100644 --- a/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py +++ b/awx_collection/plugins/modules/tower_ad_hoc_command_wait.py @@ -45,7 +45,7 @@ EXAMPLES = ''' tower_ad_hoc_command: inventory: "Demo Inventory" credential: "Demo Credential" - wait: False + wait: false register: command - name: Wait for ad joc command max 120s diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml index 9fa3d47ad2..1c45ea6b1e 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml @@ -1,19 +1,45 @@ --- -# - 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +- name: Generate names + set_fact: + inv_name: "AWX-Collection-tests-tower_tower_ad_hoc_command-inventory-{{ test_id }}" + ssh_cred_name: "AWX-Collection-tests-tower_tower_ad_hoc_command-ssh-cred-{{ test_id }}" + org_name: "AWX-Collection-tests-tower_tower_ad_hoc_command-org-{{ test_id }}" + +- name: Create a New Organization + tower_organization: + name: "{{ org_name }}" + +- name: Create an Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: present + +- name: Add localhost to the Inventory + tower_host: + name: localhost + inventory: "{{ inv_name }}" + variables: + ansible_connection: local + +- name: Create a Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: present - name: Launch an Ad Hoc Command waiting for it to finish tower_ad_hoc_command: - inventory: "Demo Inventory" - credential: "Demo Credential" + inventory: "{{ inv_name }}" + credential: "{{ ssh_cred_name }}" module_name: "command" - module_args: "echo I<3 Ansible" + module_args: "echo I <3 Ansible" wait: true register: result @@ -24,8 +50,8 @@ - name: Check module fails with correct msg tower_ad_hoc_command: - inventory: "Demo Inventory" - credential: "Demo Credential" + inventory: "{{ inv_name }}" + credential: "{{ ssh_cred_name }}" module_name: "Does not exist" register: result ignore_errors: true @@ -35,3 +61,21 @@ - "result is failed" - "result is not changed" - "'Does not exist' in result.response['json']['module_name'][0]" + +- name: Delete the Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: absent + +- name: Delete the Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: absent + +- name: Remove the Organization + tower_organization: + name: "{{ org_name }}" + state: absent diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml index 0cbc583ac3..20f11124fe 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml @@ -1,19 +1,45 @@ --- -# - 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +- name: Generate names + set_fact: + inv_name: "AWX-Collection-tests-tower_tower_ad_hoc_command_cancel-inventory-{{ test_id }}" + ssh_cred_name: "AWX-Collection-tests-tower_tower_ad_hoc_command_cancel-ssh-cred-{{ test_id }}" + org_name: "AWX-Collection-tests-tower_tower_ad_hoc_command_cancel-org-{{ test_id }}" + +- name: Create a New Organization + tower_organization: + name: "{{ org_name }}" + +- name: Create an Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: present + +- name: Add localhost to the Inventory + tower_host: + name: localhost + inventory: "{{ inv_name }}" + variables: + ansible_connection: local + +- name: Create a Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: present - name: Launch an Ad Hoc Command tower_ad_hoc_command: - inventory: "Demo Inventory" - credential: "Demo Credential" + inventory: "{{ inv_name }}" + credential: "{{ ssh_cred_name }}" module_name: "command" - module_args: "sleep 10" + module_args: "sleep 100" register: command - assert: @@ -32,9 +58,10 @@ - results is failed - "results['msg'] == 'Monitoring of ad hoc command aborted due to timeout'" -- name: Cancel the command +- name: Cancel the command with hard error if it's not running tower_ad_hoc_command_cancel: command_id: "{{ command.id }}" + fail_if_not_running: true register: results - assert: @@ -61,3 +88,21 @@ - assert: that: - "result.msg == 'Unable to find command with id 9999999999'" + +- name: Delete the Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: absent + +- name: Delete the Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: absent + +- name: Remove the Organization + tower_organization: + name: "{{ org_name }}" + state: absent diff --git a/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml index babaca0bfc..2f30de529d 100644 --- a/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml @@ -1,12 +1,38 @@ --- -# - 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 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 names -# set_fact: -# proj_name: "AWX-Collection-tests-tower_job_launch-project-{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" +- name: Generate names + set_fact: + inv_name: "AWX-Collection-tests-tower_ad_hoc_command_wait-inventory-{{ test_id }}" + ssh_cred_name: "AWX-Collection-tests-tower_ad_hoc_command_wait-ssh-cred-{{ test_id }}" + org_name: "AWX-Collection-tests-tower_ad_hoc_command_wait-org-{{ test_id }}" + +- name: Create a New Organization + tower_organization: + name: "{{ org_name }}" + +- name: Create an Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: present + +- name: Add localhost to the Inventory + tower_host: + name: localhost + inventory: "{{ inv_name }}" + variables: + ansible_connection: local + +- name: Create a Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: present - name: Check module fails with correct msg tower_ad_hoc_command_wait: @@ -21,8 +47,8 @@ - name: Launch command module with sleep 10 tower_ad_hoc_command: - inventory: "Demo Inventory" - credential: "Demo Credential" + inventory: "{{ inv_name }}" + credential: "{{ ssh_cred_name }}" module_name: "command" module_args: "sleep 5" register: command @@ -45,10 +71,10 @@ - name: Launch a long running command tower_ad_hoc_command: - inventory: "Demo Inventory" - credential: "Demo Credential" + inventory: "{{ inv_name }}" + credential: "{{ ssh_cred_name }}" module_name: "command" - module_args: "sleep 100" + module_args: "sleep 10000" register: command - assert: @@ -65,10 +91,10 @@ # Make sure that we failed and that we have some data in our results - assert: that: - - "wait_results.msg == 'Monitoring aborted due to timeout' or 'Timeout waiting for command to finish.'" + - "'Monitoring aborted due to timeout' or 'Timeout waiting for command to finish.' in wait_results.msg" - "'id' in wait_results" -- name: Async cancel the long running command +- name: Async cancel the long-running command tower_ad_hoc_command_cancel: command_id: "{{ command.id }}" async: 3600 @@ -85,3 +111,21 @@ - wait_results is failed - 'wait_results.status == "canceled"' - "wait_results.msg == 'The ad hoc command - {{ command.id }}, failed'" + +- name: Delete the Credential + tower_credential: + name: "{{ ssh_cred_name }}" + organization: "{{ org_name }}" + credential_type: 'Machine' + state: absent + +- name: Delete the Inventory + tower_inventory: + name: "{{ inv_name }}" + organization: "{{ org_name }}" + state: absent + +- name: Remove the Organization + tower_organization: + name: "{{ org_name }}" + state: absent