diff --git a/awx_collection/plugins/modules/tower_schedule.py b/awx_collection/plugins/modules/tower_schedule.py new file mode 100644 index 0000000000..6a21747db6 --- /dev/null +++ b/awx_collection/plugins/modules/tower_schedule.py @@ -0,0 +1,234 @@ +#!/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_schedule +author: "John Westcott IV (@john-westcott-iv)" +version_added: "2.3" +short_description: create, update, or destroy Ansible Tower schedules. +description: + - Create, update, or destroy Ansible Tower schedules. See + U(https://www.ansible.com/tower) for an overview. +options: + rrule: + description: + - A value representing the schedules iCal recurrence rule. + required: False + type: str + name: + description: + - Name of this schedule. + required: True + type: str + new_name: + description: + - Setting this option will change the existing name (looked up via the name field. + required: True + type: str + description: + description: + - Optional description of this schedule. + required: False + type: str + extra_data: + description: + - Specify C(extra_vars) for the template. + required: False + type: dict + default: {} + inventory: + description: + - Inventory applied as a prompt, assuming job template prompts for inventory + required: False + type: str + scm_branch: + description: + - Branch to use in job run. Project default used if blank. Only allowed if project allow_override field is set to true. + required: False + type: str + job_type: + description: + - The job type to use for the job template. + required: False + type: str + choices: + - 'run' + - 'check' + job_tags: + description: + - Comma separated list of the tags to use for the job template. + required: False + type: str + skip_tags: + description: + - Comma separated list of the tags to skip for the job template. + required: False + type: str + limit: + description: + - A host pattern to further constrain the list of hosts managed or affected by the playbook + required: False + type: str + diff_mode: + description: + - Enable diff mode for the job template. + required: False + type: bool + verbosity: + description: + - Control the output level Ansible produces as the playbook runs. 0 - Normal, 1 - Verbose, 2 - More Verbose, 3 - Debug, 4 - Connection Debug. + required: False + type: int + choices: + - 0 + - 1 + - 2 + - 3 + - 4 + - 5 + unified_job_template: + description: + - Name of unified job template to schedule. + required: False + type: str + enabled: + description: + - Enables processing of this schedule. + required: False + type: bool + state: + description: + - Desired state of the resource. + choices: ["present", "absent"] + default: "present" + type: str + tower_oauthtoken: + description: + - The Tower OAuth token to use. + required: False + type: str + version_added: "3.7" +extends_documentation_fragment: awx.awx.auth +''' + +EXAMPLES = ''' +''' + +from ..module_utils.tower_api import TowerModule + + +def main(): + # Any additional arguments that are not fields of the item can be added here + argument_spec = dict( + rrule=dict(), + name=dict(required=True), + new_name=dict(), + description=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(type='int', choices=[0, 1, 2, 3, 4, 5]), + unified_job_template=dict(), + enabled=dict(type='bool'), + state=dict(choices=['present', 'absent'], default='present'), + ) + + # Create a module for ourselves + module = TowerModule(argument_spec=argument_spec) + + # Extract our parameters + rrule = module.params.get('rrule') + name = module.params.get('name') + new_name = module.params.get("new_name") + description = module.params.get('description') + extra_data = module.params.get('extra_data') + inventory = module.params.get('inventory') + scm_branch = module.params.get('scm_branch') + job_type = module.params.get('job_type') + job_tags = module.params.get('job_tags') + skip_tags = module.params.get('skip_tags') + limit = module.params.get('limit') + diff_mode = module.params.get('diff_mode') + verbosity = module.params.get('verbosity') + unified_job_template = module.params.get('unified_job_template') + enabled = module.params.get('enabled') + state = module.params.get('state') + + # Attempt to look up the related items the user specified (these will fail the module if not found) + inventory_id = None + if inventory: + inventory_id = module.resolve_name_to_id('inventories', inventory) + unified_job_template_id = None + if unified_job_template: + unified_job_template_id = module.resolve_name_to_id('unified_job_templates', unified_job_template) + + # Attempt to look up an existing item based on the provided data + existing_item = module.get_one('schedules', **{ + 'data': { + 'name': name, + } + }) + + # Create the data that gets sent for create and update + new_fields = {} + if rrule is not None: + new_fields['rrule'] = rrule + new_fields['name'] = new_name if new_name else name + if description is not None: + new_fields['description'] = description + if extra_data is not None: + new_fields['extra_data'] = extra_data + if inventory is not None: + new_fields['inventory'] = inventory_id + if scm_branch is not None: + new_fields['scm_branch'] = scm_branch + if job_type is not None: + new_fields['job_type'] = job_type + if job_tags is not None: + new_fields['job_tags'] = job_tags + if skip_tags is not None: + new_fields['skip_tags'] = skip_tags + if limit is not None: + new_fields['limit'] = limit + if diff_mode is not None: + new_fields['diff_mode'] = diff_mode + if verbosity is not None: + new_fields['verbosity'] = verbosity + if unified_job_template is not None: + new_fields['unified_job_template'] = unified_job_template_id + if enabled is not None: + new_fields['enabled'] = enabled + + 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) + elif state == 'present': + # 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='schedules', item_type='schedule', + associations={ + } + ) + + +if __name__ == '__main__': + main() diff --git a/awx_collection/tests/integration/targets/tower_schedule/tasks/main.yml b/awx_collection/tests/integration/targets/tower_schedule/tasks/main.yml new file mode 100644 index 0000000000..3bea78f2d3 --- /dev/null +++ b/awx_collection/tests/integration/targets/tower_schedule/tasks/main.yml @@ -0,0 +1,103 @@ +--- +- name: Generate a random string for test + set_fact: + test_id: "{{ lookup('password', '/dev/null chars=ascii_letters length=16') }}" + +- name: generate random string for project + set_fact: + sched1: "AWX-Collection-tests-tower_schedule-sched1-{{ test_id }}" + +# rrule: +# description: +# extra_data: +# inventory: +# scm_branch: +# job_type: +# job_tags: +# skip_tags: +# limit: +# diff_mode: +# verbosity: + +- name: Try to create without an rrule + tower_schedule: + name: "{{ sched1 }}" + state: present + unified_job_template: "Demo Job Template" + enabled: True + register: result + ignore_errors: True + +- assert: + that: + - result is failed + - "'Unable to create schedule {{ sched1 }}' in result.msg" + +- name: Create with options that the JT does not support + tower_schedule: + name: "{{ sched1 }}" + state: present + unified_job_template: "Demo Job Template" + rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1" + description: "This hopefully will not work" + extra_data: + some: var + inventory: Demo Inventory + scm_branch: asdf1234 + job_type: run + job_tags: other_tags + skip_tags: some_tags + limit: node1 + diff_mode: True + verbosity: 4 + enabled: True + register: result + ignore_errors: True + +- assert: + that: + - result is failed + - "'Unable to create schedule {{ sched1 }}' in result.msg" + +- name: Build a real schedule + tower_schedule: + name: "{{ sched1 }}" + state: present + unified_job_template: "Demo Job Template" + rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1" + register: result + ignore_errors: True + +- assert: + that: + - result is changed + +- name: Rebuild the same scedule + tower_schedule: + name: "{{ sched1 }}" + state: present + unified_job_template: "Demo Job Template" + rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1" + register: result + ignore_errors: True + +- assert: + that: + - result is not changed + +- name: Disable a schedule + tower_schedule: + name: "{{ sched1 }}" + state: present + enabled: "False" + register: result + ignore_errors: True + +- assert: + that: + - result is changed + +- name: Delete the schedule + tower_schedule: + name: "{{ sched1 }}" + state: absent