mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Adding tower_workflow_launch (#42701)
This commit is contained in:
parent
9722254618
commit
e1b7acde87
5 changed files with 389 additions and 0 deletions
|
@ -0,0 +1,164 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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_workflow_launch
|
||||||
|
author: "John Westcott IV (@john-westcott-iv)"
|
||||||
|
version_added: "2.8"
|
||||||
|
short_description: Run a workflow in Ansible Tower
|
||||||
|
description:
|
||||||
|
- Launch an Ansible Tower workflows. See
|
||||||
|
U(https://www.ansible.com/tower) for an overview.
|
||||||
|
options:
|
||||||
|
workflow_template:
|
||||||
|
description:
|
||||||
|
- The name of the workflow template to run.
|
||||||
|
required: True
|
||||||
|
extra_vars:
|
||||||
|
description:
|
||||||
|
- Any extra vars required to launch the job.
|
||||||
|
required: False
|
||||||
|
wait:
|
||||||
|
description:
|
||||||
|
- Wait for the workflow to complete.
|
||||||
|
required: False
|
||||||
|
default: True
|
||||||
|
type: bool
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- If waiting for the workflow to complete this will abort after this
|
||||||
|
- ammount of seconds
|
||||||
|
|
||||||
|
requirements:
|
||||||
|
- "python >= 2.6"
|
||||||
|
extends_documentation_fragment: tower
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
tower_version:
|
||||||
|
description: The version of Tower we connected to
|
||||||
|
returned: If connection to Tower works
|
||||||
|
type: str
|
||||||
|
sample: '3.4.0'
|
||||||
|
job_info:
|
||||||
|
description: dictionnary containing information about the workflow executed
|
||||||
|
returned: If workflow launched
|
||||||
|
type: dict
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Launch a workflow
|
||||||
|
tower_workflow_launch:
|
||||||
|
name: "Test Workflow"
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
register: workflow_results
|
||||||
|
|
||||||
|
- name: Launch a Workflow with parameters without waiting
|
||||||
|
tower_workflow_launch:
|
||||||
|
workflow_template: "Test workflow"
|
||||||
|
extra_vars: "---\nmy: var"
|
||||||
|
wait: False
|
||||||
|
delegate_to: localhost
|
||||||
|
run_once: true
|
||||||
|
register: workflow_task_info
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.ansible_tower import TowerModule, tower_auth_config
|
||||||
|
|
||||||
|
try:
|
||||||
|
import tower_cli
|
||||||
|
from tower_cli.api import client
|
||||||
|
from tower_cli.conf import settings
|
||||||
|
from tower_cli.exceptions import ServerError, ConnectionError, BadRequest, TowerCLIError
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = dict(
|
||||||
|
workflow_template=dict(required=True),
|
||||||
|
extra_vars=dict(required=False),
|
||||||
|
wait=dict(required=False, default=True, type='bool'),
|
||||||
|
timeout=dict(required=False, default=None, type='int'),
|
||||||
|
)
|
||||||
|
|
||||||
|
module = TowerModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True
|
||||||
|
)
|
||||||
|
|
||||||
|
workflow_template = module.params.get('workflow_template')
|
||||||
|
extra_vars = module.params.get('extra_vars')
|
||||||
|
wait = module.params.get('wait')
|
||||||
|
timeout = module.params.get('timeout')
|
||||||
|
|
||||||
|
# If we are going to use this result to return we can consider ourselfs changed
|
||||||
|
result = dict(
|
||||||
|
changed=False,
|
||||||
|
msg='initial message'
|
||||||
|
)
|
||||||
|
|
||||||
|
tower_auth = tower_auth_config(module)
|
||||||
|
with settings.runtime_values(**tower_auth):
|
||||||
|
# First we will test the connection. This will be a test for both check and run mode
|
||||||
|
# Note, we are not using the tower_check_mode method here because we want to do more than just a ping test
|
||||||
|
# If we are in check mode we also want to validate that we can find the workflow
|
||||||
|
try:
|
||||||
|
ping_result = client.get('/ping').json()
|
||||||
|
# Stuff the version into the results as an FYI
|
||||||
|
result['tower_version'] = ping_result['version']
|
||||||
|
except(ServerError, ConnectionError, BadRequest) as excinfo:
|
||||||
|
result['msg'] = "Failed to reach Tower: {0}".format(excinfo)
|
||||||
|
module.fail_json(**result)
|
||||||
|
|
||||||
|
# Now that we know we can connect, lets verify that we can resolve the workflow_template
|
||||||
|
try:
|
||||||
|
workflow = tower_cli.get_resource("workflow").get(**{'name': workflow_template})
|
||||||
|
except TowerCLIError as e:
|
||||||
|
result['msg'] = "Failed to find workflow: {0}".format(e)
|
||||||
|
module.fail_json(**result)
|
||||||
|
|
||||||
|
# Since we were able to find the workflow, if we are in check mode we can return now
|
||||||
|
if module.check_mode:
|
||||||
|
result['msg'] = "Check mode passed"
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# We are no ready to run the workflow
|
||||||
|
try:
|
||||||
|
result['job_info'] = tower_cli.get_resource('workflow_job').launch(
|
||||||
|
workflow_job_template=workflow['id'],
|
||||||
|
monitor=False,
|
||||||
|
wait=wait,
|
||||||
|
timeout=timeout,
|
||||||
|
extra_vars=extra_vars
|
||||||
|
)
|
||||||
|
if wait:
|
||||||
|
# If we were waiting for a result we will fail if the workflow failed
|
||||||
|
if result['job_info']['failed']:
|
||||||
|
result['msg'] = "Workflow execution failed"
|
||||||
|
module.fail_json(**result)
|
||||||
|
else:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# We were not waiting and there should be no way we can make it here without the workflow fired off so we can return a success
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
except TowerCLIError as e:
|
||||||
|
result['msg'] = "Failed to execute workflow: {0}".format(e)
|
||||||
|
module.fail_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
2
test/integration/targets/tower_workflow_launch/aliases
Normal file
2
test/integration/targets/tower_workflow_launch/aliases
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
cloud/tower
|
||||||
|
shippable/tower/group1
|
|
@ -0,0 +1,46 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"asset_relation": {
|
||||||
|
"workflow_nodes": [
|
||||||
|
{
|
||||||
|
"name": "node0",
|
||||||
|
"unified_job_type": "job",
|
||||||
|
"success_nodes": [
|
||||||
|
"node1"
|
||||||
|
],
|
||||||
|
"failure_nodes": [],
|
||||||
|
"unified_job_name": "Demo Job Template",
|
||||||
|
"always_nodes": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "node1",
|
||||||
|
"unified_job_type": "job",
|
||||||
|
"success_nodes": [],
|
||||||
|
"failure_nodes": [],
|
||||||
|
"unified_job_name": "Demo Job Template",
|
||||||
|
"always_nodes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"team": [],
|
||||||
|
"name": "Execute",
|
||||||
|
"user": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"team": [],
|
||||||
|
"name": "Admin",
|
||||||
|
"user": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"team": [],
|
||||||
|
"name": "Read",
|
||||||
|
"user": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"survey_spec": {}
|
||||||
|
},
|
||||||
|
"asset_type": "workflow",
|
||||||
|
"name": "Success Workflow"
|
||||||
|
},
|
||||||
|
]
|
|
@ -0,0 +1,78 @@
|
||||||
|
- name: Get unified job template ID for Demo Job Template"
|
||||||
|
uri:
|
||||||
|
url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/unified_job_templates/?name=Demo+Job+Template"
|
||||||
|
method: GET
|
||||||
|
password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
user: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
|
validate_certs: False
|
||||||
|
register: unified_job
|
||||||
|
|
||||||
|
- name: Build workflow
|
||||||
|
uri:
|
||||||
|
url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/workflow_job_templates/"
|
||||||
|
body:
|
||||||
|
name: "Success Template"
|
||||||
|
variables: "---"
|
||||||
|
extra_vars: ""
|
||||||
|
body_format: 'json'
|
||||||
|
method: 'POST'
|
||||||
|
password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
status_code: 201
|
||||||
|
user: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
|
validate_certs: False
|
||||||
|
register: workflow
|
||||||
|
|
||||||
|
- name: Add a node
|
||||||
|
uri:
|
||||||
|
url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/workflow_job_templates/{{ workflow.json.id }}/workflow_nodes/"
|
||||||
|
body:
|
||||||
|
credential: null
|
||||||
|
diff_mode: null
|
||||||
|
extra_data: {}
|
||||||
|
inventory: null
|
||||||
|
job_tags: null
|
||||||
|
job_type: null
|
||||||
|
limit: null
|
||||||
|
skip_tags: null
|
||||||
|
unified_job_template: "{{ unified_job.json.results[0].id }}"
|
||||||
|
verbosity: null
|
||||||
|
body_format: 'json'
|
||||||
|
method: 'POST'
|
||||||
|
password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
status_code: 201
|
||||||
|
user: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
|
validate_certs: False
|
||||||
|
register: node1
|
||||||
|
|
||||||
|
- name: Add a node
|
||||||
|
uri:
|
||||||
|
url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/workflow_job_templates/{{ workflow.json.id }}/workflow_nodes/"
|
||||||
|
body:
|
||||||
|
credential: null
|
||||||
|
diff_mode: null
|
||||||
|
extra_data: {}
|
||||||
|
inventory: null
|
||||||
|
job_tags: null
|
||||||
|
job_type: null
|
||||||
|
limit: null
|
||||||
|
skip_tags: null
|
||||||
|
unified_job_template: "{{ unified_job.json.results[0].id }}"
|
||||||
|
verbosity: null
|
||||||
|
body_format: 'json'
|
||||||
|
method: 'POST'
|
||||||
|
password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
status_code: 201
|
||||||
|
user: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
|
validate_certs: False
|
||||||
|
register: node2
|
||||||
|
|
||||||
|
- name: "Link nodes {{ node2.json.id }} to {{ node1.json.id }}"
|
||||||
|
uri:
|
||||||
|
url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/workflow_job_template_nodes/{{ node1.json.id }}/success_nodes/"
|
||||||
|
body: '{ "id": {{ node2.json.id }} }'
|
||||||
|
body_format: 'json'
|
||||||
|
method: 'POST'
|
||||||
|
password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
|
||||||
|
status_code: 204
|
||||||
|
user: "{{ lookup('env', 'TOWER_USERNAME') }}"
|
||||||
|
validate_certs: False
|
|
@ -0,0 +1,99 @@
|
||||||
|
- name: Run a workflow with no parameters
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
ignore_errors: true
|
||||||
|
register: result1
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result1.failed
|
||||||
|
- "'missing required arguments' in result1.msg"
|
||||||
|
|
||||||
|
- name: Fail no connect to Tower server
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_host: 127.0.0.1:22
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Here"
|
||||||
|
ignore_errors: True
|
||||||
|
register: result2
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result2.failed
|
||||||
|
- "'Failed to reach Tower' in result2.msg"
|
||||||
|
|
||||||
|
- name: Connect to Tower server but request an invalid workflow
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Does Not Exist"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result3
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result3.failed
|
||||||
|
- "'The requested object could not be found' in result3.msg"
|
||||||
|
|
||||||
|
- name: Connect to Tower in check_mode with a valid workflow name
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Success Workflow"
|
||||||
|
check_mode: True
|
||||||
|
ignore_errors: true
|
||||||
|
register: result4
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not result4.failed
|
||||||
|
- "'Check mode passed' in result4.msg"
|
||||||
|
|
||||||
|
- name: Connect to Tower in check_mode with a valid workflow id
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: 9999999
|
||||||
|
check_mode: True
|
||||||
|
ignore_errors: true
|
||||||
|
register: result5
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result5.failed
|
||||||
|
- "'The requested object could not be found' in result5.msg"
|
||||||
|
|
||||||
|
- name: Run the workflow without waiting (this should just give us back a job ID)
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Success Workflow"
|
||||||
|
wait: False
|
||||||
|
ignore_errors: True
|
||||||
|
register: result6
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not result6.failed
|
||||||
|
- "'id' in result6['job_info']"
|
||||||
|
|
||||||
|
- name: Kick off a workflow and wait for it
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Success Workflow"
|
||||||
|
ignore_errors: True
|
||||||
|
register: result7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- not result7.failed
|
||||||
|
- "'id' in result7['job_info']"
|
||||||
|
|
||||||
|
- name: Kick off a workflow and wait for it, but only for a second
|
||||||
|
tower_workflow_launch:
|
||||||
|
tower_verify_ssl: False
|
||||||
|
workflow_template: "Success Workflow"
|
||||||
|
timeout: 1
|
||||||
|
ignore_errors: True
|
||||||
|
register: result8
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result8.failed
|
||||||
|
- "'Monitoring aborted due to timeout' in result8.msg"
|
Loading…
Reference in a new issue