Files
awx/awx_collection/plugins/modules/workflow_job_template_node.py
2021-10-12 13:06:30 -04:00

404 lines
15 KiB
Python

#!/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: workflow_job_template_node
author: "John Westcott IV (@john-westcott-iv)"
short_description: create, update, or destroy Automation Platform Controller workflow job template nodes.
description:
- Create, update, or destroy Automation Platform Controller workflow job template nodes.
- Use this to build a graph for a workflow, which dictates what the workflow runs.
- Replaces the deprecated tower_workflow_template module schema command.
- You can create nodes first, and link them afterwards, and not worry about ordering.
For failsafe referencing of a node, specify identifier, WFJT, and organization.
With those specified, you can choose to modify or not modify any other parameter.
options:
extra_data:
description:
- Variables to apply at launch time.
- Will only be accepted if job template prompts for vars or has a survey asking for those vars.
type: dict
default: {}
inventory:
description:
- Inventory applied as a prompt, if job template prompts for inventory
type: str
scm_branch:
description:
- SCM branch applied as a prompt, if job template prompts for SCM branch
type: str
job_type:
description:
- Job type applied as a prompt, if job template prompts for job type
type: str
choices:
- 'run'
- 'check'
job_tags:
description:
- Job tags applied as a prompt, if job template prompts for job tags
type: str
skip_tags:
description:
- Tags to skip, applied as a prompt, if job tempalte prompts for job tags
type: str
limit:
description:
- Limit to act on, applied as a prompt, if job template prompts for limit
type: str
diff_mode:
description:
- Run diff mode, applied as a prompt, if job template prompts for diff mode
type: bool
verbosity:
description:
- Verbosity applied as a prompt, if job template prompts for verbosity
type: str
choices:
- '0'
- '1'
- '2'
- '3'
- '4'
- '5'
workflow_job_template:
description:
- The workflow job template the node exists in.
- Used for looking up the node, cannot be modified after creation.
required: True
type: str
aliases:
- workflow
organization:
description:
- The organization of the workflow job template the node exists in.
- Used for looking up the workflow, not a direct model field.
type: str
unified_job_template:
description:
- Name of unified job template to run in the workflow.
- Can be a job template, project, inventory source, etc.
- Omit if creating an approval node.
- This parameter is mutually exclusive with C(approval_node).
type: str
lookup_organization:
description:
- Organization the inventories, job template, project, inventory source the unified_job_template exists in.
- If not provided, will lookup by name only, which does not work with duplicates.
type: str
approval_node:
description:
- A dictionary of Name, description, and timeout values for the approval node.
- This parameter is mutually exclusive with C(unified_job_template).
type: dict
suboptions:
name:
description:
- Name of this workflow approval template.
type: str
required: True
description:
description:
- Optional description of this workflow approval template.
type: str
timeout:
description:
- The amount of time (in seconds) before the approval node expires and fails.
type: int
all_parents_must_converge:
description:
- If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node
type: bool
identifier:
description:
- An identifier for this node that is unique within its workflow.
- It is copied to workflow job nodes corresponding to this node.
required: True
type: str
always_nodes:
description:
- Nodes that will run after this node completes.
- List of node identifiers.
type: list
elements: str
success_nodes:
description:
- Nodes that will run after this node on success.
- List of node identifiers.
type: list
elements: str
failure_nodes:
description:
- Nodes that will run after this node on failure.
- List of node identifiers.
type: list
elements: str
credentials:
description:
- Credentials to be applied to job as launch-time prompts.
- List of credential names.
- Uniqueness is not handled rigorously.
type: list
elements: str
state:
description:
- Desired state of the resource.
choices: ["present", "absent"]
default: "present"
type: str
extends_documentation_fragment: awx.awx.auth
'''
EXAMPLES = '''
- name: Create a node, follows workflow_job_template example
workflow_job_template_node:
identifier: my-first-node
workflow: example-workflow
unified_job_template: jt-for-node-use
organization: Default # organization of workflow job template
extra_data:
foo_key: bar_value
- name: Create parent node for prior node
workflow_job_template_node:
identifier: my-root-node
workflow: example-workflow
unified_job_template: jt-for-node-use
organization: Default
success_nodes:
- my-first-node
- name: Create workflow with 2 Job Templates and an approval node in between
block:
- name: Create a workflow job template
tower_workflow_job_template:
name: my-workflow-job-template
ask_scm_branch_on_launch: true
organization: Default
- name: Create 1st node
tower_workflow_job_template_node:
identifier: my-first-node
workflow_job_template: my-workflow-job-template
unified_job_template: some_job_template
organization: Default
- name: Create 2nd approval node
tower_workflow_job_template_node:
identifier: my-second-approval-node
workflow_job_template: my-workflow-job-template
organization: Default
approval_node:
description: "Do this?"
name: my-second-approval-node
timeout: 3600
- name: Create 3rd node
tower_workflow_job_template_node:
identifier: my-third-node
workflow_job_template: my-workflow-job-template
unified_job_template: some_other_job_template
organization: Default
- name: Link 1st node to 2nd Approval node
tower_workflow_job_template_node:
identifier: my-first-node
workflow_job_template: my-workflow-job-template
organization: Default
success_nodes:
- my-second-approval-node
- name: Link 2nd Approval Node 3rd node
tower_workflow_job_template_node:
identifier: my-second-approval-node
workflow_job_template: my-workflow-job-template
organization: Default
success_nodes:
- my-third-node
'''
from ..module_utils.controller_api import ControllerAPIModule
def main():
# Any additional arguments that are not fields of the item can be added here
argument_spec = dict(
identifier=dict(required=True),
workflow_job_template=dict(required=True, aliases=['workflow']),
organization=dict(),
extra_data=dict(type='dict'),
inventory=dict(),
scm_branch=dict(),
job_type=dict(choices=['run', 'check']),
job_tags=dict(),
skip_tags=dict(),
limit=dict(),
diff_mode=dict(type='bool'),
verbosity=dict(choices=['0', '1', '2', '3', '4', '5']),
unified_job_template=dict(),
lookup_organization=dict(),
approval_node=dict(type='dict'),
all_parents_must_converge=dict(type='bool'),
success_nodes=dict(type='list', elements='str'),
always_nodes=dict(type='list', elements='str'),
failure_nodes=dict(type='list', elements='str'),
credentials=dict(type='list', elements='str'),
state=dict(choices=['present', 'absent'], default='present'),
)
mutually_exclusive = [("unified_job_template", "approval_node")]
required_if = [
['state', 'absent', ['identifier']],
['state', 'present', ['identifier']],
['state', 'present', ['unified_job_template', 'approval_node', 'success_nodes', 'always_nodes', 'failure_nodes'], True],
]
# Create a module for ourselves
module = ControllerAPIModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
)
# Extract our parameters
identifier = module.params.get('identifier')
state = module.params.get('state')
approval_node = module.params.get('approval_node')
new_fields = {}
lookup_organization = module.params.get('lookup_organization')
search_fields = {'identifier': identifier}
# Attempt to look up the related items the user specified (these will fail the module if not found)
workflow_job_template = module.params.get('workflow_job_template')
workflow_job_template_id = None
if workflow_job_template:
wfjt_search_fields = {}
organization = module.params.get('organization')
if organization:
organization_id = module.resolve_name_to_id('organizations', organization)
wfjt_search_fields['organization'] = organization_id
wfjt_data = module.get_one('workflow_job_templates', name_or_id=workflow_job_template, **{'data': wfjt_search_fields})
if wfjt_data is None:
module.fail_json(
msg="The workflow {0} in organization {1} was not found on the controller instance server".format(workflow_job_template, organization)
)
workflow_job_template_id = wfjt_data['id']
search_fields['workflow_job_template'] = new_fields['workflow_job_template'] = workflow_job_template_id
# Attempt to look up an existing item based on the provided data
existing_item = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
if state == 'absent':
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
module.delete_if_needed(existing_item)
# Set lookup data to use
search_fields = {}
if lookup_organization:
search_fields['organization'] = module.resolve_name_to_id('organizations', lookup_organization)
unified_job_template = module.params.get('unified_job_template')
if unified_job_template:
new_fields['unified_job_template'] = module.get_one('unified_job_templates', name_or_id=unified_job_template, **{'data': search_fields})['id']
inventory = module.params.get('inventory')
if inventory:
new_fields['inventory'] = module.resolve_name_to_id('inventories', inventory)
# Create the data that gets sent for create and update
for field_name in (
'identifier',
'extra_data',
'scm_branch',
'job_type',
'job_tags',
'skip_tags',
'limit',
'diff_mode',
'verbosity',
'all_parents_must_converge',
):
field_val = module.params.get(field_name)
if field_val:
new_fields[field_name] = field_val
association_fields = {}
for association in ('always_nodes', 'success_nodes', 'failure_nodes', 'credentials'):
name_list = module.params.get(association)
if name_list is None:
continue
id_list = []
for sub_name in name_list:
if association == 'credentials':
endpoint = 'credentials'
lookup_data = {'name': sub_name}
else:
endpoint = 'workflow_job_template_nodes'
lookup_data = {'identifier': sub_name}
if workflow_job_template_id:
lookup_data['workflow_job_template'] = workflow_job_template_id
sub_obj = module.get_one(endpoint, **{'data': lookup_data})
if sub_obj is None:
module.fail_json(msg='Could not find {0} entry with name {1}'.format(association, sub_name))
id_list.append(sub_obj['id'])
if id_list:
association_fields[association] = id_list
# In the case of a new object, the utils need to know it is a node
new_fields['type'] = 'workflow_job_template_node'
# If the state was present and we can let the module build or update the existing item, this will return on its own
module.create_or_update_if_needed(
existing_item,
new_fields,
endpoint='workflow_job_template_nodes',
item_type='workflow_job_template_node',
auto_exit=not approval_node,
associations=association_fields,
)
# Create approval node unified template or update existing
if approval_node:
# Set Approval Fields
new_fields = {}
# Extract Parameters
if approval_node.get('name') is None:
module.fail_json(msg="Approval node name is required to create approval node.")
if approval_node.get('name') is not None:
new_fields['name'] = approval_node['name']
if approval_node.get('description') is not None:
new_fields['description'] = approval_node['description']
if approval_node.get('timeout') is not None:
new_fields['timeout'] = approval_node['timeout']
# Find created workflow node ID
search_fields = {'identifier': identifier}
search_fields['workflow_job_template'] = workflow_job_template_id
workflow_job_template_node = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
workflow_job_template_node_id = workflow_job_template_node['id']
module.json_output['workflow_node_id'] = workflow_job_template_node_id
existing_item = None
# Due to not able to lookup workflow_approval_templates, find the existing item in another place
if workflow_job_template_node['related'].get('unified_job_template') is not None:
existing_item = module.get_endpoint(workflow_job_template_node['related']['unified_job_template'])['json']
approval_endpoint = 'workflow_job_template_nodes/{0}/create_approval_template/'.format(workflow_job_template_node_id)
module.create_or_update_if_needed(
existing_item, new_fields, endpoint=approval_endpoint, item_type='workflow_job_template_approval_node', associations=association_fields
)
module.exit_json(**module.json_output)
if __name__ == '__main__':
main()