summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsoftwarefactory-project-zuul[bot] <33884098+softwarefactory-project-zuul[bot]@users.noreply.github.com>2020-09-29 16:59:52 +0200
committerGitHub <noreply@github.com>2020-09-29 16:59:52 +0200
commitf39015156bc7720d042df85de04a0db11241c711 (patch)
tree44d15f44cd372ac078c22963ab684d6750514c50
parentMerge pull request #8254 from RULCSoft/fix-typos (diff)
parentUpdate integration tests (diff)
downloadawx-f39015156bc7720d042df85de04a0db11241c711.tar.xz
awx-f39015156bc7720d042df85de04a0db11241c711.zip
Merge pull request #8228 from john-westcott-iv/tower_ad_hoc_module
Adding ad hoc command modules Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
-rw-r--r--awx_collection/plugins/modules/tower_ad_hoc_command.py187
-rw-r--r--awx_collection/plugins/modules/tower_ad_hoc_command_cancel.py130
-rw-r--r--awx_collection/plugins/modules/tower_ad_hoc_command_wait.py127
-rw-r--r--awx_collection/test/awx/test_ad_hoc_wait.py55
-rw-r--r--awx_collection/test/awx/test_completeness.py6
-rw-r--r--awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml81
-rw-r--r--awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml108
-rw-r--r--awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml131
-rw-r--r--awx_collection/tools/vars/resolution.yml1
9 files changed, 824 insertions, 2 deletions
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 <john.westcott.iv@redhat.com>
+# 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 <wayne@riotousliving.com>
+# 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..7d1cc19418
--- /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 <wayne@riotousliving.com>
+# 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..3626c42239 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,13 +48,15 @@ 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
# 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'],
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..1c45ea6b1e
--- /dev/null
+++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command/tasks/main.yml
@@ -0,0 +1,81 @@
+---
+- 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:
+ 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: "{{ inv_name }}"
+ credential: "{{ ssh_cred_name }}"
+ 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: "{{ inv_name }}"
+ credential: "{{ ssh_cred_name }}"
+ 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]"
+
+- 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
new file mode 100644
index 0000000000..20f11124fe
--- /dev/null
+++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_cancel/tasks/main.yml
@@ -0,0 +1,108 @@
+---
+- 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:
+ 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: "{{ inv_name }}"
+ credential: "{{ ssh_cred_name }}"
+ module_name: "command"
+ module_args: "sleep 100"
+ 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 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:
+ 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'"
+
+- 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
new file mode 100644
index 0000000000..2f30de529d
--- /dev/null
+++ b/awx_collection/tests/integration/targets/tower_ad_hoc_command_wait/tasks/main.yml
@@ -0,0 +1,131 @@
+---
+- 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:
+ 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:
+ 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: "{{ inv_name }}"
+ credential: "{{ ssh_cred_name }}"
+ 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: "{{ inv_name }}"
+ credential: "{{ ssh_cred_name }}"
+ module_name: "command"
+ module_args: "sleep 10000"
+ 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:
+ - "'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
+ 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'"
+
+- 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/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