diff --git a/awx_collection/plugins/module_utils/tower_api.py b/awx_collection/plugins/module_utils/tower_api.py index 7439cbfb8f..cb31f428a4 100644 --- a/awx_collection/plugins/module_utils/tower_api.py +++ b/awx_collection/plugins/module_utils/tower_api.py @@ -410,6 +410,51 @@ 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, copy_from_name_or_id, new_item_name, endpoint=None, item_type='unknown', copy_lookup_data=None): + + if existing_item is not None: + self.warn(msg="A {0} with the name {1} already exists.".format(item_type, new_item_name)) + self.json_output['changed'] = False + self.json_output['copied'] = False + return existing_item + + # Lookup existing item to copy from + copy_from_lookup = self.get_one(endpoint, name_or_id=copy_from_name_or_id, **{'data': copy_lookup_data}) + + # Fail if the copy_from_lookup is empty + if copy_from_lookup is None: + self.fail_json(msg="A {0} with the name {1} was not able to be found.".format(item_type, copy_from_name_or_id)) + + # Do checks for copy permisions if warrented + if item_type == 'workflow_job_template': + copy_get_check = self.get_endpoint(copy_from_lookup['related']['copy']) + if copy_get_check['status_code'] in [200]: + if (copy_get_check['json']['can_copy'] and copy_get_check['json']['can_copy_without_user_input'] and not + copy_get_check['json']['templates_unable_to_copy'] and not copy_get_check['json']['credentials_unable_to_copy'] and not + copy_get_check['json']['inventories_unable_to_copy']): + # Because checks have passed + self.json_output['copy_checks'] = 'passed' + else: + self.fail_json(msg="Unable to copy {0} {1} error: {2}".format(item_type, copy_from_name_or_id, copy_get_check)) + else: + self.fail_json(msg="Error accessing {0} {1} error: {2} ".format(item_type, copy_from_name_or_id, copy_get_check)) + + response = self.post_endpoint(copy_from_lookup['related']['copy'], **{'data': {'name': new_item_name}}) + + if response['status_code'] in [201]: + self.json_output['id'] = response['json']['id'] + self.json_output['changed'] = True + self.json_output['copied'] = True + new_existing_item = response['json'] + else: + if 'json' in response and '__all__' in response['json']: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, new_item_name, response['json']['__all__'][0])) + elif 'json' in response: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, new_item_name, response['json'])) + else: + self.fail_json(msg="Unable to create {0} {1}: {2}".format(item_type, new_item_name, response['status_code'])) + return new_existing_item + 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..a3609b818d 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') @@ -382,11 +398,22 @@ def main(): lookup_data = { 'credential_type': cred_type_id, } + # Create a copy of lookup data for copying without org. + copy_lookup_data = lookup_data if organization: lookup_data['organization'] = org_id credential = module.get_one('credentials', name_or_id=name, **{'data': lookup_data}) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + credential = module.copy_item( + credential, copy_from, name, + endpoint='credentials', item_type='credential', + copy_lookup_data=copy_lookup_data, + ) + 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(credential) 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..1ec8c5d271 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') @@ -115,6 +133,15 @@ def main(): } }) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + inventory = module.copy_item( + inventory, copy_from, name, + endpoint='inventories', item_type='inventory', + copy_lookup_data={}, + ) + 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(inventory) diff --git a/awx_collection/plugins/modules/tower_job_template.py b/awx_collection/plugins/modules/tower_job_template.py index bb52b4d4ee..094de9127b 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. @@ -314,6 +322,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 @@ -339,6 +356,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']), @@ -392,6 +410,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 @@ -424,6 +443,15 @@ def main(): # 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}) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + existing_item = module.copy_item( + existing_item, copy_from, name, + endpoint='job_templates', item_type='job_template', + copy_lookup_data={}, + ) + 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_notification_template.py b/awx_collection/plugins/modules/tower_notification_template.py index 95e5971e79..26f41787e8 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') @@ -386,6 +402,15 @@ def main(): } }) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + existing_item = module.copy_item( + existing_item, copy_from, name, + endpoint='notification_templates', item_type='notification_template', + copy_lookup_data={}, + ) + 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 42264996cb..408aa203e9 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. @@ -160,7 +168,7 @@ EXAMPLES = ''' state: present tower_config_file: "~/tower_cli.cfg" -- name: Add Tower Project with cache timeout and custom virtualenv +- name: Add Tower Project with cache timeout tower_project: name: "Foo" description: "Foo bar project" @@ -169,6 +177,14 @@ EXAMPLES = ''' scm_update_cache_timeout: 60 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 @@ -223,6 +239,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(), @@ -252,21 +269,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') default_ee = module.params.get('default_environment') organization = module.params.get('organization') state = module.params.get('state') @@ -284,6 +294,15 @@ def main(): # 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) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + project = module.copy_item( + project, copy_from, name, + endpoint='projects', item_type='project', + copy_lookup_data={}, + ) + 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(project) @@ -316,26 +335,27 @@ 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, } - 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 default_ee is not None: project_fields['default_environment'] = module.resolve_name_to_id('execution_environments', default_ee) - 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 48759e1cc4..6faa4c1353 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. @@ -146,6 +154,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 @@ -172,6 +186,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(), @@ -202,6 +217,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 = {} @@ -220,6 +236,15 @@ def main(): # 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}) + # Attempt to look up credential to copy based on the provided name + if copy_from: + # a new existing item is formed when copying and is returned. + existing_item = module.copy_item( + existing_item, copy_from, name, + endpoint='workflow_job_templates', item_type='workflow_job_template', + copy_lookup_data={}, + ) + 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/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index 7ff6e22a31..cb34660959 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -30,7 +30,7 @@ no_endpoint_for_module = [ # Global module parameters we can ignore ignore_parameters = [ - 'state', 'new_name', 'update_secrets' + 'state', 'new_name', 'update_secrets', 'copy_from' ] # Some modules take additional parameters that do not appear in the API @@ -48,8 +48,10 @@ no_api_parameter_ok = { 'tower_workflow_job_template_node': ['organization', 'approval_node'], # Survey is how we handle associations 'tower_workflow_job_template': ['survey_spec'], - # ad hoc commands support interval and timeout since its more like tower_job_launc + # ad hoc commands support interval and timeout since its more like tower_job_launch 'tower_ad_hoc_command': ['interval', 'timeout', 'wait'], + # tower_group parameters to perserve hosts and children. + 'tower_group': ['preserve_existing_children', 'preserve_existing_hosts'], } # When this tool was created we were not feature complete. Adding something in here indicates a module 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 aba3765461..4adc162778 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.copied + +- 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 }}" 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..5184e94d5b 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..234f2f15a4 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 }}" @@ -49,6 +48,30 @@ 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.copied + + - 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: name: "{{ inv_name1 }}" @@ -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..af43b7d325 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.copied + +- 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..95d66c1c51 100644 --- a/awx_collection/tests/integration/targets/tower_project/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_project/tasks/main.yml @@ -75,20 +75,21 @@ 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 # the project, or it could be because the module actually failed somehow - assert: that: - - "result is changed" + - result.copied - name: Check module fails with correct msg when given non-existing org as param tower_project: 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 6380dad1fb..0b2374831c 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 @@ -241,6 +241,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.copied + +- 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 }}"