From aaa157f140a469bc14e289b2400ea17d208f5b7a Mon Sep 17 00:00:00 2001 From: Adrien Fleury Date: Wed, 29 Aug 2018 19:58:35 +0200 Subject: [PATCH] New module tower_workflow_template. (#37520) * Add new module *tower_workflow_template* Manage Tower workflows and their schemas. --- .../ansible_tower/tower_workflow_template.py | 191 ++++++++++++++++++ .../targets/tower_workflow_template/aliases | 2 + .../tower_workflow_template/tasks/main.yml | 80 ++++++++ 3 files changed, 273 insertions(+) create mode 100644 lib/ansible/modules/web_infrastructure/ansible_tower/tower_workflow_template.py create mode 100644 test/integration/targets/tower_workflow_template/aliases create mode 100644 test/integration/targets/tower_workflow_template/tasks/main.yml diff --git a/lib/ansible/modules/web_infrastructure/ansible_tower/tower_workflow_template.py b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_workflow_template.py new file mode 100644 index 0000000000..3454f432d9 --- /dev/null +++ b/lib/ansible/modules/web_infrastructure/ansible_tower/tower_workflow_template.py @@ -0,0 +1,191 @@ +#!/usr/bin/python +# coding: utf-8 -*- +# +# (c) 2018, Adrien Fleury +# 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 = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.1'} + + +DOCUMENTATION = ''' +--- +module: tower_workflow_template +author: "Adrien Fleury (@fleu42)" +version_added: "2.7" +short_description: create, update, or destroy Ansible Tower workflow template. +description: + - Create, update, or destroy Ansible Tower workflows. See + U(https://www.ansible.com/tower) for an overview. +options: + allow_simultaneous: + description: + - If enabled, simultaneous runs of this job template will be allowed. + required: False + type: bool + description: + description: + - The description to use for the workflow. + required: False + default: null + extra_vars: + description: + - > + Extra variables used by Ansible in YAML or key=value format. + required: False + name: + description: + - The name to use for the workflow. + required: True + organization: + description: + - The organization the workflow is linked to. + required: False + schema: + description: + - > + The schema is a JSON- or YAML-formatted string defining the + hierarchy structure that connects the nodes. Refer to Tower + documentation for more information. + required: False + survey_enabled: + description: + - Setting that variable will prompt the user for job type on the + workflow launch. + required: False + type: bool + survey: + description: + - The definition of the survey associated to the workflow. + required: False + state: + description: + - Desired state of the resource. + required: False + default: "present" + choices: ["present", "absent"] +extends_documentation_fragment: tower +''' + + +EXAMPLES = ''' +- tower_workflow_template: + name: Workflow Template + description: My very first Worflow Template + organization: My optional Organization + schema: "{{ lookup(file, my_workflow.json }}" + +- tower_worflow_template: + name: Workflow Template + state: absent +''' + + +RETURN = ''' # ''' + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ansible_tower import ( + TowerModule, + tower_auth_config, + tower_check_mode +) + +try: + import tower_cli + import tower_cli.exceptions as exc + from tower_cli.conf import settings +except ImportError: + pass + + +def main(): + argument_spec = dict( + name=dict(required=True), + description=dict(required=False), + extra_vars=dict(required=False), + organization=dict(required=False), + allow_simultaneous=dict(type='bool', required=False), + schema=dict(required=False), + survey=dict(required=False), + survey_enabled=dict(type='bool', required=False), + state=dict(choices=['present', 'absent'], default='present'), + ) + + module = TowerModule( + argument_spec=argument_spec, + supports_check_mode=False + ) + + name = module.params.get('name') + state = module.params.get('state') + + schema = None + if module.params.get('schema'): + schema = module.params.get('schema') + + if schema and state == 'absent': + module.fail_json( + msg='Setting schema when state is absent is not allowed', + changed=False + ) + + json_output = {'workflow_template': name, 'state': state} + + tower_auth = tower_auth_config(module) + with settings.runtime_values(**tower_auth): + tower_check_mode(module) + wfjt_res = tower_cli.get_resource('workflow') + params = {} + params['name'] = name + + if module.params.get('description'): + params['description'] = module.params.get('description') + + if module.params.get('organization'): + organization_res = tower_cli.get_resource('organization') + try: + organization = organization_res.get( + name=module.params.get('organization')) + params['organization'] = organization['id'] + except exc.NotFound as excinfo: + module.fail_json( + msg='Failed to update organization source,' + 'organization not found: {0}'.format(excinfo), + changed=False + ) + + if module.params.get('survey'): + params['survey_spec'] = module.params.get('survey') + + for key in ('allow_simultaneous', 'extra_vars', 'survey_enabled', + 'description'): + if module.params.get(key): + params[key] = module.params.get(key) + + try: + if state == 'present': + params['create_on_missing'] = True + result = wfjt_res.modify(**params) + json_output['id'] = result['id'] + if schema: + wfjt_res.schema(result['id'], schema) + elif state == 'absent': + params['fail_on_missing'] = False + result = wfjt_res.delete(**params) + except (exc.ConnectionError, exc.BadRequest) as excinfo: + module.fail_json(msg='Failed to update workflow template: \ + {0}'.format(excinfo), changed=False) + + json_output['changed'] = result['changed'] + module.exit_json(**json_output) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/tower_workflow_template/aliases b/test/integration/targets/tower_workflow_template/aliases new file mode 100644 index 0000000000..229eebe6c9 --- /dev/null +++ b/test/integration/targets/tower_workflow_template/aliases @@ -0,0 +1,2 @@ +cloud/tower +shippable/tower/group1 diff --git a/test/integration/targets/tower_workflow_template/tasks/main.yml b/test/integration/targets/tower_workflow_template/tasks/main.yml new file mode 100644 index 0000000000..8eba26c134 --- /dev/null +++ b/test/integration/targets/tower_workflow_template/tasks/main.yml @@ -0,0 +1,80 @@ +--- +- name: Create an SCM Credential + tower_credential: + name: SCM Credential for JT + organization: Default + kind: scm + +- name: Create a Demo Project + tower_project: + name: Job Template Test Project + organization: Default + state: present + scm_type: git + scm_url: https://github.com/ansible/ansible-tower-samples.git + scm_credential: SCM Credential for JT + register: result + +- name: Update the project (to clone the git repo) + uri: + url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/projects/{{ result.id }}/update/" + method: POST + user: "{{ lookup('env', 'TOWER_USERNAME') }}" + password: "{{ lookup('env', 'TOWER_PASSWORD') }}" + validate_certs: false + status_code: 202 + force_basic_auth: true + +- name: Wait for the project to be status=successful + uri: + url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2/projects/{{ result.id }}/" + method: GET + user: "{{ lookup('env', 'TOWER_USERNAME') }}" + password: "{{ lookup('env', 'TOWER_PASSWORD') }}" + validate_certs: false + force_basic_auth: true + return_content: true + register: result + until: result.json.status == "successful" + retries: 60 + delay: 1 + +- name: Create a Job Template + tower_job_template: + name: my-job-1 + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + +- name: Create a second Job Template + tower_job_template: + name: my-job-2 + project: Job Template Test Project + inventory: Demo Inventory + playbook: hello_world.yml + credential: Demo Credential + job_type: run + state: present + +- name: Create a workflow job template + tower_workflow_template: + name: my-workflow + schema: '[{"success": [{"job_template": "my-job-1"}], "job_template": "my-job-2"}]' + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a workflow job template + tower_workflow_template: + name: my-workflow + state: absent + register: result + +- assert: + that: + - "result is changed"