diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 7439cbfb8f..b419ebac68 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -410,6 +410,27 @@ class TowerAPIModule(TowerModule): else: self.fail_json(msg="Failed to associate item {0}".format(response['json'].get('detail', response['json']))) + def copy_item(self, existing_item, name, item_type='unknown'): + + if not existing_item: + self.fail_json(msg="Unable to create copy {0} due to missing endpoint".format(item_type)) + item_url = existing_item['related']['copy'] + response = self.post_endpoint(item_url, **{'data': {'name': name}}) + + if response['status_code'] in [201]: + self.json_output['id'] = response['json']['id'] + self.json_output['changed'] = True + item_url = response['json']['url'] + else: + if 'json' in response and '__all__' in response['json']: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, name, response['json']['__all__'][0])) + elif 'json' in response: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, name, response['json'])) + else: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, name, response['status_code'])) + last_data = response['json'] + return last_data + def create_if_needed(self, existing_item, new_item, endpoint, on_create=None, auto_exit=True, item_type='unknown', associations=None): # This will exit from the module on its own diff --git a/awx_collection/plugins/modules/tower_credential.py b/awx_collection/plugins/modules/tower_credential.py index 76032c8a02..5002c4e0ed 100644 --- a/awx_collection/plugins/modules/tower_credential.py +++ b/awx_collection/plugins/modules/tower_credential.py @@ -32,6 +32,14 @@ options: - Setting this option will change the existing name (looked up via the name field. required: False type: str + copy_from: + description: + - Name or id to copy the credential from. + - This will copy an existing credential and change any parameters supplied. + - The new credential name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple credentials share the same name. + type: str description: description: - The description to use for the credential. @@ -274,6 +282,12 @@ EXAMPLES = ''' vault_password: 'new_password' vault_id: 'My ID' +- name: Copy Credential + tower_credential: + name: Copy password + copy_from: Example password + credential_type: Vault + organization: Foo ''' from ..module_utils.tower_api import TowerAPIModule @@ -318,6 +332,7 @@ def main(): argument_spec = dict( name=dict(required=True), new_name=dict(), + copy_from=dict(), description=dict(), organization=dict(), credential_type=dict(), @@ -356,6 +371,7 @@ def main(): # Extract our parameters name = module.params.get('name') new_name = module.params.get('new_name') + copy_from = module.params.get('copy_from') description = module.params.get('description') organization = module.params.get('organization') credential_type = module.params.get('credential_type') @@ -385,6 +401,28 @@ def main(): if organization: lookup_data['organization'] = org_id + # Attempt to look up credential to copy based on the provided name + if copy_from: + # Check if credential exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + credential = module.get_one('credentials', name_or_id=name, **{'data': lookup_data}) + if credential is not None: + module.fail_json(msg="A credential with the name {0} already exists.".format(name)) + else: + # Lookup existing credential. + copy_lookup_data = { + 'credential_type': cred_type_id, + } + copy_from_lookup = module.get_one('credentials', name_or_id=copy_from, **{'data': copy_lookup_data}) + if copy_from_lookup is None: + module.fail_json(msg="A credential with the name {0} was not able to be found.".format(copy_from)) + else: + # Because the initial copy will keep its organization, this can be different then the specified one. + lookup_data['organization'] = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='credential' + ) + credential = module.get_one('credentials', name_or_id=name, **{'data': lookup_data}) if state == 'absent': diff --git a/awx_collection/plugins/modules/tower_group.py b/awx_collection/plugins/modules/tower_group.py index 6781dc5883..e93fc02c09 100644 --- a/awx_collection/plugins/modules/tower_group.py +++ b/awx_collection/plugins/modules/tower_group.py @@ -52,6 +52,18 @@ options: elements: str aliases: - groups + preserve_existing_hosts: + description: + - Provide option (False by default) to preserves existing hosts in an existing group in tower. + default: False + type: bool + preserve_existing_children: + description: + - Provide option (False by default) to preserves existing children in an existing group in tower. + default: False + type: bool + aliases: + - preserve_existing_groups state: description: - Desired state of the resource. @@ -74,6 +86,18 @@ EXAMPLES = ''' inventory: "Local Inventory" state: present tower_config_file: "~/tower_cli.cfg" + +- name: Add tower group + tower_group: + name: Cities + description: "Local Host Group" + inventory: Default Inventory + hosts: + - fda + children: + - NewYork + preserve_existing_hosts: True + preserve_existing_children: True ''' from ..module_utils.tower_api import TowerAPIModule @@ -90,6 +114,8 @@ def main(): variables=dict(type='dict'), hosts=dict(type='list', elements='str'), children=dict(type='list', elements='str', aliases=['groups']), + preserve_existing_hosts=dict(type='bool', default=False), + preserve_existing_children=dict(type='bool', default=False, aliases=['preserve_existing_groups']), state=dict(choices=['present', 'absent'], default='present'), ) @@ -102,6 +128,8 @@ def main(): inventory = module.params.get('inventory') description = module.params.get('description') state = module.params.pop('state') + preserve_existing_hosts = module.params.get('preserve_existing_hosts') + preserve_existing_children = module.params.get('preserve_existing_groups') variables = module.params.get('variables') # Attempt to look up the related items the user specified (these will fail the module if not found) @@ -141,6 +169,11 @@ def main(): if sub_obj is None: module.fail_json(msg='Could not find {0} with name {1}'.format(resource, sub_name)) id_list.append(sub_obj['id']) + # Preserve existing objects + if (preserve_existing_hosts and relationship == 'hosts') or (preserve_existing_children and relationship == 'children'): + preserve_existing_check = module.get_endpoint(group['related'][relationship]) + for sub_obj in preserve_existing_check['json']['results']: + id_list.append(sub_obj['id']) if id_list: association_fields[relationship] = id_list diff --git a/awx_collection/plugins/modules/tower_inventory.py b/awx_collection/plugins/modules/tower_inventory.py index ce4eab93d8..f3ad98184c 100644 --- a/awx_collection/plugins/modules/tower_inventory.py +++ b/awx_collection/plugins/modules/tower_inventory.py @@ -27,6 +27,14 @@ options: - The name to use for the inventory. required: True type: str + copy_from: + description: + - Name or id to copy the inventory from. + - This will copy an existing inventory and change any parameters supplied. + - The new inventory name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple inventories share the same name. + type: str description: description: - The description to use for the inventory. @@ -72,6 +80,14 @@ EXAMPLES = ''' organization: "Bar Org" state: present tower_config_file: "~/tower_cli.cfg" + +- name: Copy tower inventory + tower_inventory: + name: Copy Foo Inventory + copy_from: Default Inventory + description: "Our Foo Cloud Servers" + organization: Foo + state: present ''' @@ -83,6 +99,7 @@ def main(): # Any additional arguments that are not fields of the item can be added here argument_spec = dict( name=dict(required=True), + copy_from=dict(), description=dict(), organization=dict(required=True), variables=dict(type='dict'), @@ -97,6 +114,7 @@ def main(): # Extract our parameters name = module.params.get('name') + copy_from = module.params.get('copy_from') description = module.params.get('description') organization = module.params.get('organization') variables = module.params.get('variables') @@ -108,6 +126,29 @@ def main(): # Attempt to look up the related items the user specified (these will fail the module if not found) org_id = module.resolve_name_to_id('organizations', organization) + # Attempt to look up inventory to copy based on the provided name + if copy_from: + # Check if inventory exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + inventory = module.get_one('inventories', name_or_id=name, **{ + 'data': { + 'organization': org_id + } + }) + if inventory is not None: + module.fail_json(msg="A inventory with the name {0} already exists.".format(name)) + else: + # Lookup existing inventory. + copy_from_lookup = module.get_one('inventories', name_or_id=copy_from) + if copy_from_lookup is None: + module.fail_json(msg="An inventory with the name {0} was not able to be found.".format(copy_from)) + else: + # Because the initial copy will keep its organization, this can be different then the specified one. + org_id = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='inventory' + ) + # Attempt to look up inventory based on the provided name and org ID inventory = module.get_one('inventories', name_or_id=name, **{ 'data': { @@ -119,6 +160,9 @@ def main(): # 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(inventory) + # Reset Org id to push in case copy_from was used. + org_id = module.resolve_name_to_id('organizations', organization) + # Create the data that gets sent for create and update inventory_fields = { 'name': module.get_item_name(inventory) if inventory else name, diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index 787c145a20..47a4820ba5 100644 --- a/awx_collection/plugins/modules/tower_job_template.py +++ b/awx_collection/plugins/modules/tower_job_template.py @@ -31,6 +31,14 @@ options: description: - Setting this option will change the existing name (looed up via the name field. type: str + copy_from: + description: + - Name or id to copy the job template from. + - This will copy an existing job template and change any parameters supplied. + - The new job template name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple job templates share the same name. + type: str description: description: - Description to use for the job template. @@ -315,6 +323,15 @@ EXAMPLES = ''' notification_templates_started: - Notification2 +- name: Copy Job Template + tower_job_template: + name: copy job template + copy_from: test job template + job_type: "run" + inventory: Copy Foo Inventory + project: test + playbook: hello_world.yml + state: "present" ''' from ..module_utils.tower_api import TowerAPIModule @@ -340,6 +357,7 @@ def main(): argument_spec = dict( name=dict(required=True), new_name=dict(), + copy_from=dict(), description=dict(), organization=dict(), job_type=dict(choices=['run', 'check']), @@ -393,6 +411,7 @@ def main(): # Extract our parameters name = module.params.get('name') new_name = module.params.get("new_name") + copy_from = module.params.get('copy_from') state = module.params.get('state') # Deal with legacy credential and vault_credential @@ -418,6 +437,25 @@ def main(): organization_id = module.resolve_name_to_id('organizations', organization) search_fields['organization'] = new_fields['organization'] = organization_id + # Attempt to look up job template to copy based on the provided name + if copy_from: + # Check if job template exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + job_template = module.get_one('job_templates', name_or_id=name, **{'data': search_fields}) + if job_template is not None: + module.fail_json(msg="A job template with the name {0} already exists.".format(name)) + else: + # Lookup existing job template. + copy_from_lookup = module.get_one('job_templates', name_or_id=copy_from) + if copy_from_lookup is None: + module.fail_json(msg="A job template with the name {0} was not able to be found.".format(copy_from)) + else: + # Because the initial copy will keep its organization, this can be different then the specified one. + search_fields['organization'] = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='job_template' + ) + # Attempt to look up an existing item based on the provided data existing_item = module.get_one('job_templates', name_or_id=name, **{'data': search_fields}) diff --git a/awx_collection/plugins/modules/tower_notification_template.py b/awx_collection/plugins/modules/tower_notification_template.py index 95e5971e79..63520664ad 100644 --- a/awx_collection/plugins/modules/tower_notification_template.py +++ b/awx_collection/plugins/modules/tower_notification_template.py @@ -31,6 +31,14 @@ options: description: - Setting this option will change the existing name (looked up via the name field. type: str + copy_from: + description: + - Name or id to copy the notification from. + - This will copy an existing notification and change any parameters supplied. + - The new notification name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple notifications share the same name. + type: str description: description: - The description of the notification. @@ -294,6 +302,12 @@ EXAMPLES = ''' name: old notification state: absent tower_config_file: "~/tower_cli.cfg" + +- name: Copy webhook notification + tower_notification_template: + name: foo notification + copy_from: email notification + organization: Foo ''' @@ -318,6 +332,7 @@ def main(): argument_spec = dict( name=dict(required=True), new_name=dict(), + copy_from=dict(), description=dict(), organization=dict(), notification_type=dict(choices=[ @@ -360,6 +375,7 @@ def main(): # Extract our parameters name = module.params.get('name') new_name = module.params.get('new_name') + copy_from = module.params.get('copy_from') description = module.params.get('description') organization = module.params.get('organization') notification_type = module.params.get('notification_type') @@ -379,6 +395,29 @@ def main(): if organization: organization_id = module.resolve_name_to_id('organizations', organization) + # Attempt to look up notification template to copy based on the provided name + if copy_from: + # Check if notification template exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + notification_template = module.get_one('notification_templates', name_or_id=name, **{ + 'data': { + 'organization': organization_id + } + }) + if notification_template is not None: + module.fail_json(msg="A notification template with the name {0} already exists.".format(name)) + else: + # Lookup existing notification template. + copy_from_lookup = module.get_one('notification_templates', name_or_id=copy_from) + if copy_from_lookup is None: + module.fail_json(msg="An notification template with the name {0} was not able to be found.".format(copy_from)) + else: + # Because the initial copy will keep its organization, this can be different then the specified one. + organization_id = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='notification_template' + ) + # Attempt to look up an existing item based on the provided data existing_item = module.get_one('notification_templates', name_or_id=name, **{ 'data': { @@ -386,6 +425,10 @@ def main(): } }) + # Reset Org id to push in case copy_from was used. + if organization: + organization_id = module.resolve_name_to_id('organizations', organization) + 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) diff --git a/awx_collection/plugins/modules/tower_project.py b/awx_collection/plugins/modules/tower_project.py index 76cef63f10..cccc2fec79 100644 --- a/awx_collection/plugins/modules/tower_project.py +++ b/awx_collection/plugins/modules/tower_project.py @@ -27,6 +27,14 @@ options: - Name to use for the project. required: True type: str + copy_from: + description: + - Name or id to copy the project from. + - This will copy an existing project and change any parameters supplied. + - The new project name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple projects share the same name. + type: str description: description: - Description to use for the project. @@ -171,6 +179,14 @@ EXAMPLES = ''' custom_virtualenv: "/var/lib/awx/var/lib/awx/venv/ansible-2.2" state: present tower_config_file: "~/tower_cli.cfg" + +- name: Copy tower project + tower_project: + name: copy + copy_from: test + description: Foo copy project + organization: Foo + state: present ''' import time @@ -225,6 +241,7 @@ def main(): # Any additional arguments that are not fields of the item can be added here argument_spec = dict( name=dict(required=True), + copy_from=dict(), description=dict(), scm_type=dict(choices=['manual', 'git', 'svn', 'insights'], default='manual'), scm_url=dict(), @@ -254,22 +271,14 @@ def main(): # Extract our parameters name = module.params.get('name') - description = module.params.get('description') + copy_from = module.params.get('copy_from') scm_type = module.params.get('scm_type') if scm_type == "manual": scm_type = "" - scm_url = module.params.get('scm_url') local_path = module.params.get('local_path') - scm_branch = module.params.get('scm_branch') - scm_refspec = module.params.get('scm_refspec') credential = module.params.get('credential') - scm_clean = module.params.get('scm_clean') - scm_delete_on_update = module.params.get('scm_delete_on_update') scm_update_on_launch = module.params.get('scm_update_on_launch') scm_update_cache_timeout = module.params.get('scm_update_cache_timeout') - allow_override = module.params.get('allow_override') - timeout = module.params.get('timeout') - custom_virtualenv = module.params.get('custom_virtualenv') organization = module.params.get('organization') state = module.params.get('state') wait = module.params.get('wait') @@ -283,6 +292,25 @@ def main(): org_id = module.resolve_name_to_id('organizations', organization) lookup_data['organization'] = org_id + # Attempt to look up project to copy based on the provided name + if copy_from: + # Check if project exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + project = module.get_one('projects', name_or_id=name, data=lookup_data) + if project is not None: + module.fail_json(msg="A project with the name {0} already exists.".format(name)) + else: + # Lookup existing project. + copy_from_lookup = module.get_one('projects', name_or_id=copy_from) + if copy_from_lookup is None: + module.fail_json(msg="A project with the name {0} was not able to be found.".format(copy_from)) + else: + # Because the initial copy will keep its organization, this can be different then the specified one. + lookup_data['organization'] = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='project' + ) + # Attempt to look up project based on the provided name and org ID project = module.get_one('projects', name_or_id=name, data=lookup_data) @@ -318,25 +346,23 @@ def main(): project_fields = { 'name': module.get_item_name(project) if project else name, 'scm_type': scm_type, - 'scm_url': scm_url, - 'scm_branch': scm_branch, - 'scm_refspec': scm_refspec, - 'scm_clean': scm_clean, - 'scm_delete_on_update': scm_delete_on_update, - 'timeout': timeout, 'organization': org_id, - 'scm_update_on_launch': scm_update_on_launch, - 'scm_update_cache_timeout': scm_update_cache_timeout, - 'custom_virtualenv': custom_virtualenv, } - if description is not None: - project_fields['description'] = description + + for field_name in ( + 'scm_url', 'scm_branch', 'scm_refspec', 'scm_clean', 'scm_delete_on_update', + 'timeout', 'scm_update_cache_timeout', 'custom_virtualenv', + 'description', 'allow_override', + ): + field_val = module.params.get(field_name) + if field_val is not None: + project_fields[field_name] = field_val + if credential is not None: project_fields['credential'] = credential - if allow_override is not None: - project_fields['allow_override'] = allow_override if scm_type == '': - project_fields['local_path'] = local_path + if local_path is not None: + project_fields['local_path'] = local_path if scm_update_cache_timeout != 0 and scm_update_on_launch is not True: module.warn('scm_update_cache_timeout will be ignored since scm_update_on_launch was not set to true') diff --git a/awx_collection/plugins/modules/tower_workflow_job_template.py b/awx_collection/plugins/modules/tower_workflow_job_template.py index 7836b42cc4..fa0c921830 100644 --- a/awx_collection/plugins/modules/tower_workflow_job_template.py +++ b/awx_collection/plugins/modules/tower_workflow_job_template.py @@ -32,6 +32,14 @@ options: description: - Setting this option will change the existing name. type: str + copy_from: + description: + - Name or id to copy the workflow job template from. + - This will copy an existing workflow job template and change any parameters supplied. + - The new workflow job template name will be the one provided in the name parameter. + - The organization parameter is not used in this, to facilitate copy from one organization to another. + - Provide the id or use the lookup plugin to provide the id if multiple workflow job templates share the same name. + type: str description: description: - Optional description of this workflow job template. @@ -142,6 +150,12 @@ EXAMPLES = ''' name: example-workflow description: created by Ansible Playbook organization: Default + +- name: Copy a workflow job template + tower_workflow_job_template: + name: copy-workflow + copy_from: example-workflow + organization: Foo ''' from ..module_utils.tower_api import TowerAPIModule @@ -168,6 +182,7 @@ def main(): argument_spec = dict( name=dict(required=True), new_name=dict(), + copy_from=dict(), description=dict(), extra_vars=dict(type='dict'), organization=dict(), @@ -197,6 +212,7 @@ def main(): # Extract our parameters name = module.params.get('name') new_name = module.params.get("new_name") + copy_from = module.params.get('copy_from') state = module.params.get('state') new_fields = {} @@ -208,6 +224,34 @@ def main(): organization_id = module.resolve_name_to_id('organizations', organization) search_fields['organization'] = new_fields['organization'] = organization_id + # Attempt to look up workflow job template to copy based on the provided name + if copy_from: + # Check if workflow job template exists, as API will allow you to create an identical item with the same name in same org, but GUI will not. + workflow_job_template = module.get_one('workflow_job_templates', name_or_id=name, data=search_fields) + if workflow_job_template is not None: + module.fail_json(msg="A workflow job template with the name {0} already exists.".format(name)) + else: + # Lookup existing workflow job template. + copy_from_lookup = module.get_one('workflow_job_templates', name_or_id=copy_from) + if copy_from_lookup is None: + module.fail_json(msg="A workflow job template with the name {0} was not able to be found.".format(copy_from)) + else: + workflow_copy_get_check = module.get_endpoint(copy_from_lookup['related']['copy']) + if workflow_copy_get_check['status_code'] in [200]: + if (workflow_copy_get_check['json']['can_copy'] and workflow_copy_get_check['json']['can_copy_without_user_input'] and + not workflow_copy_get_check['json']['templates_unable_to_copy'] and not workflow_copy_get_check['json']['credentials_unable_to_copy'] + and not workflow_copy_get_check['json']['inventories_unable_to_copy']): + # Because the initial copy will keep its organization, this can be different then the specified one. + search_fields['organization'] = copy_from_lookup['organization'] + module.copy_item( + copy_from_lookup, name, + item_type='workflow_job_template' + ) + else: + module.fail_json(msg="Unable to copy workflow {0} error: {1}".format(copy_from, workflow_copy_get_check)) + else: + module.fail_json(msg="Error accessing workflow {0} error: {1} ".format(copy_from, workflow_copy_get_check)) + # Attempt to look up an existing item based on the provided data existing_item = module.get_one('workflow_job_templates', name_or_id=name, **{'data': search_fields}) diff --git a/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml index 1303efa468..836b0054f4 100644 --- a/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_credential/tasks/main.yml @@ -203,6 +203,29 @@ that: - result is not changed +- name: Copy ssh Credential + tower_credential: + name: "copy_{{ ssh_cred_name2 }}" + copy_from: "{{ ssh_cred_name2 }}" + credential_type: Machine + register: result + +- assert: + that: + - result is changed + +- name: Delete an SSH credential + tower_credential: + name: "copy_{{ ssh_cred_name2 }}" + organization: Default + state: absent + credential_type: Machine + register: result + +- assert: + that: + - "result is changed" + - name: Create a valid SSH credential from lookup source (old school) tower_credential: name: "{{ ssh_cred_name3 }}" @@ -369,7 +392,7 @@ - result is failed - "'Unable to create credential {{ vault_cred_name2 }}' in result.msg" - "'Additional properties are not allowed' in result.msg" - - "'\\'vault_password\\' was unexpected' in result.msg" + - "'\\'vault_********\\' was unexpected' in result.msg" - name: Delete a Vault credential tower_credential: diff --git a/awx_collection/tests/integration/targets/tower_group/tasks/main.yml b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml index c0f17dbb40..c2b0351bec 100644 --- a/awx_collection/tests/integration/targets/tower_group/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_group/tasks/main.yml @@ -29,9 +29,79 @@ that: - "result is changed" +- name: Create a Group + tower_group: + name: "{{ group_name2 }}" + inventory: "{{ inv_name }}" + state: present + variables: + foo: bar + register: result + +- assert: + that: + - "result is changed" + +- name: Create a Group + tower_group: + name: "{{ group_name3 }}" + inventory: "{{ inv_name }}" + state: present + variables: + foo: bar + register: result + +- assert: + that: + - "result is changed" + +- name: add hosts + tower_host: + name: "{{ item }}" + inventory: "{{ inv_name }}" + loop: + - "{{ host_name1 }}" + - "{{ host_name2 }}" + - "{{ host_name3 }}" + +- name: Create a Group with hosts and sub group + tower_group: + name: "{{ group_name1 }}" + inventory: "{{ inv_name }}" + hosts: + - "{{ host_name1 }}" + - "{{ host_name2 }}" + children: + - "{{ group_name2 }}" + state: present + variables: + foo: bar + register: result + +- name: Create a Group with hosts and sub group + tower_group: + name: "{{ group_name1 }}" + inventory: "{{ inv_name }}" + hosts: + - "{{ host_name3 }}" + children: + - "{{ group_name3 }}" + state: present + preserve_existing_hosts: True + preserve_existing_children: True + register: result + +- name: "Find number of hosts in {{ group_name1 }}" + set_fact: + group1_host_count: "{{ lookup('awx.awx.tower_api', 'groups/{{result.id}}/all_hosts/') |length}}" + +- assert: + that: + - group1_host_count == "3" + - name: Delete a Group tower_group: - name: "{{ result.id }}" + name: "{{ group_name1 }}" inventory: "{{ inv_name }}" state: absent register: result @@ -40,6 +110,28 @@ that: - "result is changed" +- name: Delete a Group + tower_group: + name: "{{ group_name2 }}" + inventory: "{{ inv_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + +- name: Delete a Group + tower_group: + name: "{{ group_name3 }}" + inventory: "{{ inv_name }}" + state: absent + register: result + +- assert: + that: + - "result is not changed" + - name: Check module fails with correct msg tower_group: name: test-group diff --git a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml index a92fb9c426..0a9034b911 100644 --- a/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_inventory/tasks/main.yml @@ -36,7 +36,6 @@ - assert: that: - "result is changed" - - name: Test Inventory module idempotency tower_inventory: name: "{{ result.id }}" @@ -48,6 +47,30 @@ - assert: that: - "result is not changed" + + - name: Copy an inventory + tower_inventory: + name: "copy_{{ inv_name1 }}" + copy_from: "{{ inv_name1 }}" + organization: Default + description: "Our Foo Cloud Servers" + state: present + register: result + + - assert: + that: + - "result is changed" + + - name: Delete an Inventory + tower_inventory: + name: "copy_{{ inv_name1 }}" + organization: Default + state: absent + register: result + + - assert: + that: + - "result is changed" - name: Fail Change Regular to Smart tower_inventory: @@ -133,6 +156,7 @@ loop: - "{{ inv_name1 }}" - "{{ inv_name2 }}" + - "copy_{{ inv_name1 }}" - name: Delete Insights Credential tower_credential: diff --git a/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml index 6b3717d5e5..d3924b1a4a 100644 --- a/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_job_template/tasks/main.yml @@ -155,6 +155,19 @@ that: - "result is changed" +- name: Copy Job Template + tower_job_template: + name: "copy_{{ jt1 }}" + copy_from: "{{ jt1 }}" + state: "present" + +- name: Delete copied Job Template + tower_job_template: + name: "copy_{{ jt1 }}" + job_type: run + state: absent + register: result + # This doesnt work if you include the credentials parameter - name: Delete Job Template 1 tower_job_template: diff --git a/awx_collection/tests/integration/targets/tower_notification_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_notification_template/tasks/main.yml index 1ab3af794c..0d1f6478d4 100644 --- a/awx_collection/tests/integration/targets/tower_notification_template/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_notification_template/tasks/main.yml @@ -133,6 +133,28 @@ that: - result is changed +- name: Copy email notification + tower_notification_template: + name: "copy_{{ email_not }}" + copy_from: "{{ email_not }}" + organization: Default + register: result + +- assert: + that: + - result is changed + +- name: Delete copied email notification + tower_notification_template: + name: "copy_{{ email_not }}" + organization: Default + state: absent + register: result + +- assert: + that: + - result is changed + - name: Delete email notification tower_notification_template: name: "{{ email_not }}" diff --git a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml index eb1c54e5e0..8a701984ac 100644 --- a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml @@ -75,13 +75,14 @@ scm_credential: "{{ cred_name }}" check_mode: true -- name: Create a new test project +- name: "Copy tower project from {{ project_name1 }}" tower_project: name: "{{ project_name2 }}" + copy_from: "{{ project_name1 }}" organization: "{{ org_name }}" scm_type: git - scm_url: https://github.com/ansible/test-playbooks scm_credential: "{{ cred_name }}" + state: present register: result # If this fails it may be because the check_mode task actually already created diff --git a/awx_collection/tests/integration/targets/tower_workflow_job_template/tasks/main.yml b/awx_collection/tests/integration/targets/tower_workflow_job_template/tasks/main.yml index 2d916f584d..8ef01658dd 100644 --- a/awx_collection/tests/integration/targets/tower_workflow_job_template/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_workflow_job_template/tasks/main.yml @@ -244,6 +244,52 @@ that: - "result is changed" +- name: Copy a workflow job template + tower_workflow_job_template: + name: "copy_{{ wfjt_name }}" + copy_from: "{{ wfjt_name }}" + organization: Default + register: result + +- assert: + that: + - "result is changed" + +- name: Fail Remove "on start" webhook notification from copied workflow job template + tower_workflow_job_template: + name: "copy_{{ wfjt_name }}" + notification_templates_started: + - "{{ email_not }}123" + register: remove_copied_workflow_node + ignore_errors: true + +- assert: + that: + - "remove_copied_workflow_node is failed" + - "remove_copied_workflow_node is not changed" + - "'returned 0 items' in remove_copied_workflow_node.msg" + +- name: Remove "on start" webhook notification from copied workflow job template + tower_workflow_job_template: + name: "copy_{{ wfjt_name }}" + notification_templates_started: + - "{{ email_not }}" + register: result + +- assert: + that: + - "result is changed" + +- name: Delete copied workflow job template + tower_workflow_job_template: + name: "copy_{{ wfjt_name }}" + state: absent + register: result + +- assert: + that: + - "result is changed" + - name: Remove "on start" webhook notification from workflow job template tower_workflow_job_template: name: "{{ wfjt_name }}"