From 094b3b0327f70a5419a06d74ce387f6076ce5227 Mon Sep 17 00:00:00 2001 From: ssrish Date: Mon, 20 Jul 2020 12:07:24 -0700 Subject: [PATCH 1/4] new backup module --- plugins/module_utils/mso.py | 16 + plugins/modules/mso_backup.py | 200 +++++++++++ tests/integration/targets/mso_backup/aliases | 2 + .../targets/mso_backup/tasks/main.yml | 311 ++++++++++++++++++ 4 files changed, 529 insertions(+) create mode 100644 plugins/modules/mso_backup.py create mode 100644 tests/integration/targets/mso_backup/aliases create mode 100644 tests/integration/targets/mso_backup/tasks/main.yml diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index 59582513..baa6ce5c 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -438,6 +438,22 @@ def lookup_tenant(self, tenant): self.module.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t)) return t.get('id') + def lookup_remote_location(self, remote_location_name): + ''' Look up a remote location and return its path and id ''' + if remote_location_name is None: + return None + + remote_info = [] + remotes = self.query_objs('platform/remote-locations', key='remoteLocations', name=remote_location_name) + for remote in remotes: + if remote.get('name') == remote_location_name: + remote_info.append(remote.get('id')) + remote_path = remote.get('credential')['remotePath'] + remote_info.append(remote_path) + else: + self.module.fail_json(msg="Remote location lookup failed for remote '%s'" % (remote_location_name)) + return remote_info + def lookup_users(self, users): ''' Look up users and return their ids ''' # Ensure tenant has at least admin user diff --git a/plugins/modules/mso_backup.py b/plugins/modules/mso_backup.py new file mode 100644 index 00000000..80cd8bf0 --- /dev/null +++ b/plugins/modules/mso_backup.py @@ -0,0 +1,200 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish (@shrsr) +# GNU General Public License v3.0+ (see LICENSE 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 = r''' +--- +module: mso_backup +short_description: Manage backups +description: +- Manage backups on Cisco ACI Multi-Site. +author: +- Shreyas Srish (@shrsr) +options: + location_type: + description: + - The type of location for the backup to be stored + type: str + choices: [ local, remote] + default: local + backup: + description: + - The name given to the backup + type: str + aliases: [ name ] + remote_location_name: + description: + - The remote location's name for the backup to be stored + type: str + backup_remote_path: + description: + - The sub directory for the backup to be stored + type: str + description: + description: + - Brief information about the backup + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: cisco.mso.modules +''' + +EXAMPLES = r''' +- name: Create a new backup in a local location + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + description: via Ansible + location_type: local + state: present + delegate_to: localhost + +- name: Create a new backup in a remote location + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + description: via Ansible + location_type: remote + remote_location_name: ansible_test + state: present + delegate_to: localhost + +- name: Remove a Backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + state: absent + delegate_to: localhost + +- name: Query a backup + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup + state: query + delegate_to: localhost + register: query_result + +- name: Query all backups + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +''' + +RETURN = r''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec + + +def main(): + argument_spec = mso_argument_spec() + argument_spec.update( + location_type=dict(type='str', default='local', choices=['local', 'remote']), + description=dict(type='str'), + backup=dict(type='str', aliases=['name']), + remote_location_name=dict(type='str'), + backup_remote_path=dict(type='str'), + state=dict(type='str', default='present', choices=['absent', 'present', 'query']), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + description = module.params.get('description') + location_type = module.params.get('location_type') + state = module.params.get('state') + backup = module.params.get('backup') + remote_location_name = module.params.get('remote_location_name') + backup_remote_path = module.params.get('backup_remote_path') + + mso = MSOModule(module) + + if remote_location_name: + remote_location_id = (mso.lookup_remote_location(remote_location_name))[0] + remote_path = (mso.lookup_remote_location(remote_location_name))[1] + + if backup_remote_path: + remote_path += '/' + backup_remote_path + + backup_id = None + path = 'backups' + mso.existing = mso.query_objs('backups/backupRecords', key='backupRecords') + if backup: + if mso.existing: + data = mso.existing + mso.existing = [] + for backup_info in data: + backup_id = backup_info.get('id') + if backup == backup_info.get('name').split('_')[0]: + path = 'backups/backupRecords/{id}'.format(id=backup_id) + mso.existing.append(backup_info) + if len(mso.existing) < 1: + mso.existing = mso.get_obj(path, name=backup) + + if state == 'query': + mso.exit_json() + + if state == 'absent': + mso.previous = mso.existing + if mso.existing: + if module.check_mode: + mso.existing = {} + else: + mso.existing = mso.request(path, method='DELETE') + mso.exit_json() + + path = 'backups' + mso.existing = mso.get_obj(path, name=backup) + + if state == 'present': + mso.previous = mso.existing + + payload = dict( + name=backup, + description=description, + locationType=location_type + ) + if location_type == 'remote': + payload.update(remoteLocationId=remote_location_id, remotePath=remote_path) + + mso.sanitize(payload, collate=True) + + if module.check_mode: + mso.existing = mso.proposed + else: + mso.existing = mso.request(path, method='POST', data=mso.sent) + + mso.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/mso_backup/aliases b/tests/integration/targets/mso_backup/aliases new file mode 100644 index 00000000..5042c9c0 --- /dev/null +++ b/tests/integration/targets/mso_backup/aliases @@ -0,0 +1,2 @@ +# No ACI MultiSite infrastructure, so not enabled +# unsupported diff --git a/tests/integration/targets/mso_backup/tasks/main.yml b/tests/integration/targets/mso_backup/tasks/main.yml new file mode 100644 index 00000000..85b02106 --- /dev/null +++ b/tests/integration/targets/mso_backup/tasks/main.yml @@ -0,0 +1,311 @@ +# Test code for the MSO modules +# Copyright: (c) 2020, Shreyas Srish (@shrsr) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI MultiSite host, username and password + fail: + msg: 'Please define the following variables: mso_hostname, mso_username and mso_password.' + when: mso_hostname is not defined or mso_username is not defined or mso_password is not defined + +# CLEAN ENVIRONMENT +- name: Set vars + set_fact: + mso_info: &mso_info + host: '{{ mso_hostname }}' + username: '{{ mso_username }}' + password: '{{ mso_password }}' + validate_certs: '{{ mso_validate_certs | default(false) }}' + use_ssl: '{{ mso_use_ssl | default(true) }}' + use_proxy: '{{ mso_use_proxy | default(true) }}' + output_level: '{{ mso_output_level | default("info") }}' + +# Clean Env for this run +- name: Remove ansibleBackup1 + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackup2 + mso_backup: + <<: *mso_info + backup: ansibleBackup2 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackup3 + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackup3 again + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackupRemote1 + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackupRemote1 again + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: absent + ignore_errors: yes + +- name: Remove ansibleBackupRemote1 again + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: absent + ignore_errors: yes + +# Create Backups +- name: Create ansibleBackup1 in check mode + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + description: via Ansible + location_type: local + state: present + register: cm_add_ansibleBackup1 + check_mode: yes + +- name: Verify cm_add_ansibleBackup1 + assert: + that: + - cm_add_ansibleBackup1 is changed + +- name: Create ansibleBackup1 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup1 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup1 + +- name: Verify nm_add_ansibleBackup1 + assert: + that: + - nm_add_ansibleBackup1.current.backupEntry.metadata.name is match("ansibleBackup1_[0-9a-zA-Z]*") + +- name: Create ansibleBackup2 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup2 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup2 + +- name: Verify nm_add_ansibleBackup2 + assert: + that: + - nm_add_ansibleBackup2.current.backupEntry.metadata.name is match ("ansibleBackup2_[0-9a-zA-Z]*") + +- name: Create ansibleBackup3 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup3 + +- name: Verify nm_add_ansibleBackup3 + assert: + that: + - nm_add_ansibleBackup3.current.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + +- name: Create ansibleBackup3 in normal mode again + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + description: via Ansible + location_type: local + state: present + register: nm_add_ansibleBackup3_again + +- name: Verify nm_add_ansibleBackup3_again + assert: + that: + - nm_add_ansibleBackup3_again.current.backupEntry.metadata.name is match ("ansibleBackup3_[0-9a-zA-Z]*") + +- name: Create ansibleBackupRemote1 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + description: Remote via Ansible + location_type: remote + remote_location_name: ansible_test + state: present + register: nm_add_ansibleBackupRemote1 + +- name: Verify nm_add_ansibleBackupRemote1 + assert: + that: + - nm_add_ansibleBackupRemote1.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") + +- name: Create ansibleBackupRemote1 in normal mode again + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + description: Remote via Ansible + location_type: remote + remote_location_name: ansible_test + state: present + register: nm_add_ansibleBackupRemote1_again + +- name: Verify nm_add_ansibleBackupRemote1_again + assert: + that: + - nm_add_ansibleBackupRemote1_again.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") + +- name: Create ansibleBackupRemote1 in normal mode again in known path + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + description: Remote via Ansible + location_type: remote + remote_location_name: ansible_test + backup_remote_path: 2 + state: present + ignore_errors: yes + register: nm_add_ansibleBackupRemote1_again_known_path + +- name: Verify nm_add_ansibleBackupRemote1_again_known_path + assert: + that: + - nm_add_ansibleBackupRemote1_again_known_path.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") + ignore_errors: yes + +# Query a Backup +- name: Query ansibleBackup3 + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: query + register: query_ansibleBackup3 + +- name: Verify query_ansibleBackup3 + assert: + that: + - query_ansibleBackup3 is not changed + - query_ansibleBackup3.current | length == 2 + +# Query All +- name: Query All + mso_backup: + <<: *mso_info + state: query + ignore_errors: yes + register: query_all_backups + +- name: Verify query_all_backups + assert: + that: + - query_all_backups is not changed + - query_all_backups.current | length == 7 + +# Remove Backup +- name: Remove first ansibleBackup3 in check mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + check_mode: yes + register: rm_b3_cm + +- name: Verify rm_b3_cm + assert: + that: + - rm_b3_cm is changed + +- name: Remove first ansibleBackup3 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + register: nm_remove_ansibleBackup3 + +- name: Verify nm_remove_ansibleBackup3 + assert: + that: + - nm_remove_ansibleBackup3 is changed + +- name: Remove second ansibleBackup3 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + register: nm_remove_ansibleBackup3_2 + +- name: Verify nm_remove_ansibleBackup3_2 + assert: + that: + - nm_remove_ansibleBackup3_2 is changed + +- name: Remove ansibleBackup3 again in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackup3 + state: absent + ignore_errors: yes + register: nm_remove_ansibleBackup3_again + +- name: Verify nm_remove_ansibleBackup3_again + assert: + that: + - nm_remove_ansibleBackup3_again is not changed + +- name: Remove first ansibleBackupRemote1 in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: absent + ignore_errors: yes + register: nm_remove_ansibleBackupRemote1 + +- name: Verify nm_remove_ansibleBackupRemote1 + assert: + that: + - nm_remove_ansibleBackupRemote1 is changed + +- name: Remove ansibleBackupRemote1 again in normal mode + mso_backup: + <<: *mso_info + backup: ansibleBackupRemote1 + state: absent + ignore_errors: yes + register: nm_remove_ansibleBackupRemote1_2 + +- name: Verify nm_remove_ansibleBackupRemote1_2 + assert: + that: + - nm_remove_ansibleBackupRemote1_2 is changed + +- name: Query non_existent_backup + mso_backup: + <<: *mso_info + backup: nonExistentBackup + state: query + register: query_non_existent_backup + +- name: Verify query_non_existent_backup + assert: + that: + - query_non_existent_backup is not changed \ No newline at end of file From 116202a4462296eb58725c2e3296c9778897afeb Mon Sep 17 00:00:00 2001 From: ssrish Date: Mon, 20 Jul 2020 14:03:33 -0700 Subject: [PATCH 2/4] Sanity checks passed --- plugins/module_utils/mso.py | 4 ++-- plugins/modules/mso_backup.py | 4 ++-- tests/integration/targets/mso_backup/tasks/main.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index baa6ce5c..6bd1b694 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -442,7 +442,7 @@ def lookup_remote_location(self, remote_location_name): ''' Look up a remote location and return its path and id ''' if remote_location_name is None: return None - + remote_info = [] remotes = self.query_objs('platform/remote-locations', key='remoteLocations', name=remote_location_name) for remote in remotes: @@ -451,7 +451,7 @@ def lookup_remote_location(self, remote_location_name): remote_path = remote.get('credential')['remotePath'] remote_info.append(remote_path) else: - self.module.fail_json(msg="Remote location lookup failed for remote '%s'" % (remote_location_name)) + self.module.fail_json(msg="Remote location lookup failed for remote '%s'" % (remote_location_name)) return remote_info def lookup_users(self, users): diff --git a/plugins/modules/mso_backup.py b/plugins/modules/mso_backup.py index 80cd8bf0..2c44485d 100644 --- a/plugins/modules/mso_backup.py +++ b/plugins/modules/mso_backup.py @@ -14,7 +14,7 @@ DOCUMENTATION = r''' --- module: mso_backup -short_description: Manage backups +short_description: Manages backups description: - Manage backups on Cisco ACI Multi-Site. author: @@ -36,7 +36,7 @@ - The remote location's name for the backup to be stored type: str backup_remote_path: - description: + description: - The sub directory for the backup to be stored type: str description: diff --git a/tests/integration/targets/mso_backup/tasks/main.yml b/tests/integration/targets/mso_backup/tasks/main.yml index 85b02106..7ea7c1a4 100644 --- a/tests/integration/targets/mso_backup/tasks/main.yml +++ b/tests/integration/targets/mso_backup/tasks/main.yml @@ -179,7 +179,7 @@ description: Remote via Ansible location_type: remote remote_location_name: ansible_test - backup_remote_path: 2 + backup_remote_path: '2' state: present ignore_errors: yes register: nm_add_ansibleBackupRemote1_again_known_path From 71c60f6e449183f0979fb6f04eb84acef5aab281 Mon Sep 17 00:00:00 2001 From: ssrish Date: Tue, 21 Jul 2020 15:02:46 -0700 Subject: [PATCH 3/4] Code changes --- plugins/module_utils/mso.py | 17 +- plugins/modules/mso_backup.py | 63 ++++---- .../targets/mso_backup/tasks/main.yml | 151 +++++------------- 3 files changed, 78 insertions(+), 153 deletions(-) diff --git a/plugins/module_utils/mso.py b/plugins/module_utils/mso.py index 6bd1b694..732e38af 100644 --- a/plugins/module_utils/mso.py +++ b/plugins/module_utils/mso.py @@ -438,20 +438,15 @@ def lookup_tenant(self, tenant): self.module.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t)) return t.get('id') - def lookup_remote_location(self, remote_location_name): + def lookup_remote_location(self, remote_location): ''' Look up a remote location and return its path and id ''' - if remote_location_name is None: + if remote_location is None: return None - remote_info = [] - remotes = self.query_objs('platform/remote-locations', key='remoteLocations', name=remote_location_name) - for remote in remotes: - if remote.get('name') == remote_location_name: - remote_info.append(remote.get('id')) - remote_path = remote.get('credential')['remotePath'] - remote_info.append(remote_path) - else: - self.module.fail_json(msg="Remote location lookup failed for remote '%s'" % (remote_location_name)) + remote = self.get_obj('platform/remote-locations', key='remoteLocations', name=remote_location) + if 'id' not in remote: + self.module.fail_json(msg="No remote location found for remote '%s'" % (remote_location)) + remote_info = dict(id=remote.get('id'), path=remote.get('credential')['remotePath']) return remote_info def lookup_users(self, users): diff --git a/plugins/modules/mso_backup.py b/plugins/modules/mso_backup.py index 2c44485d..4611ac0c 100644 --- a/plugins/modules/mso_backup.py +++ b/plugins/modules/mso_backup.py @@ -31,17 +31,18 @@ - The name given to the backup type: str aliases: [ name ] - remote_location_name: + remote_location: description: - The remote location's name for the backup to be stored type: str - backup_remote_path: + remote_path: description: - - The sub directory for the backup to be stored + - This path is relative to the remote location. + - There's no need to specify '/' for this path. type: str description: description: - - Brief information about the backup + - Brief information about the backup. type: str state: description: @@ -54,7 +55,7 @@ ''' EXAMPLES = r''' -- name: Create a new backup in a local location +- name: Create a new local backup cisco.mso.mso_backup: host: mso_host username: admin @@ -65,7 +66,7 @@ state: present delegate_to: localhost -- name: Create a new backup in a remote location +- name: Create a new remote backup cisco.mso.mso_backup: host: mso_host username: admin @@ -73,7 +74,7 @@ backup: Backup description: via Ansible location_type: remote - remote_location_name: ansible_test + remote_location: ansible_test state: present delegate_to: localhost @@ -119,61 +120,56 @@ def main(): location_type=dict(type='str', default='local', choices=['local', 'remote']), description=dict(type='str'), backup=dict(type='str', aliases=['name']), - remote_location_name=dict(type='str'), - backup_remote_path=dict(type='str'), + remote_location=dict(type='str'), + remote_path=dict(type='str'), state=dict(type='str', default='present', choices=['absent', 'present', 'query']), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + required_if=[ + ['location_type', 'remote', ['remote_location']], + ['state', 'absent', ['backup']], + ['state', 'present', ['backup']] + ] ) description = module.params.get('description') location_type = module.params.get('location_type') state = module.params.get('state') backup = module.params.get('backup') - remote_location_name = module.params.get('remote_location_name') - backup_remote_path = module.params.get('backup_remote_path') + remote_location = module.params.get('remote_location') + remote_path = module.params.get('remote_path') mso = MSOModule(module) - if remote_location_name: - remote_location_id = (mso.lookup_remote_location(remote_location_name))[0] - remote_path = (mso.lookup_remote_location(remote_location_name))[1] - - if backup_remote_path: - remote_path += '/' + backup_remote_path - - backup_id = None - path = 'backups' + backup_names = [] mso.existing = mso.query_objs('backups/backupRecords', key='backupRecords') if backup: if mso.existing: data = mso.existing mso.existing = [] for backup_info in data: - backup_id = backup_info.get('id') - if backup == backup_info.get('name').split('_')[0]: - path = 'backups/backupRecords/{id}'.format(id=backup_id) + if backup == backup_info.get('name').split('_')[0] or backup == backup_info.get('name'): mso.existing.append(backup_info) - if len(mso.existing) < 1: - mso.existing = mso.get_obj(path, name=backup) + backup_names.append(backup_info.get('name')) if state == 'query': mso.exit_json() if state == 'absent': mso.previous = mso.existing - if mso.existing: + if len(mso.existing) > 1: + mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(', '.join(backup_names))) + elif len(mso.existing) == 1: if module.check_mode: mso.existing = {} else: - mso.existing = mso.request(path, method='DELETE') + mso.existing = mso.request('backups/backupRecords/{id}'.format(id=mso.existing[0].get('id')), method='DELETE') mso.exit_json() path = 'backups' - mso.existing = mso.get_obj(path, name=backup) if state == 'present': mso.previous = mso.existing @@ -183,15 +179,20 @@ def main(): description=description, locationType=location_type ) + if location_type == 'remote': - payload.update(remoteLocationId=remote_location_id, remotePath=remote_path) + remote_location_info = mso.lookup_remote_location(remote_location) + payload.update(remoteLocationId=remote_location_info.get('id')) + if remote_path: + remote_path = '{0}/{1}'.format(remote_location_info.get('path'), remote_path) + payload.update(remotePath=remote_path) - mso.sanitize(payload, collate=True) + mso.proposed = payload if module.check_mode: mso.existing = mso.proposed else: - mso.existing = mso.request(path, method='POST', data=mso.sent) + mso.existing = mso.request(path, method='POST', data=payload) mso.exit_json() diff --git a/tests/integration/targets/mso_backup/tasks/main.yml b/tests/integration/targets/mso_backup/tasks/main.yml index 7ea7c1a4..0924976c 100644 --- a/tests/integration/targets/mso_backup/tasks/main.yml +++ b/tests/integration/targets/mso_backup/tasks/main.yml @@ -20,55 +20,24 @@ use_proxy: '{{ mso_use_proxy | default(true) }}' output_level: '{{ mso_output_level | default("info") }}' -# Clean Env for this run -- name: Remove ansibleBackup1 +- name: Query all backups mso_backup: <<: *mso_info - backup: ansibleBackup1 - state: absent - ignore_errors: yes - -- name: Remove ansibleBackup2 - mso_backup: - <<: *mso_info - backup: ansibleBackup2 - state: absent - ignore_errors: yes - -- name: Remove ansibleBackup3 - mso_backup: - <<: *mso_info - backup: ansibleBackup3 - state: absent - ignore_errors: yes - -- name: Remove ansibleBackup3 again - mso_backup: - <<: *mso_info - backup: ansibleBackup3 - state: absent - ignore_errors: yes - -- name: Remove ansibleBackupRemote1 - mso_backup: - <<: *mso_info - backup: ansibleBackupRemote1 - state: absent - ignore_errors: yes + state: query + register: query_ansibleBackup_for_delete -- name: Remove ansibleBackupRemote1 again +- name: Remove backups with starting with name ansibleBackup mso_backup: <<: *mso_info - backup: ansibleBackupRemote1 + backup: '{{ item.name }}' state: absent - ignore_errors: yes + loop: '{{ query_ansibleBackup_for_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list }}' -- name: Remove ansibleBackupRemote1 again +- name: Query all backups post mso_backup: <<: *mso_info - backup: ansibleBackupRemote1 - state: absent - ignore_errors: yes + state: query + register: query_ansibleBackup_for_post # Create Backups - name: Create ansibleBackup1 in check mode @@ -148,7 +117,7 @@ backup: ansibleBackupRemote1 description: Remote via Ansible location_type: remote - remote_location_name: ansible_test + remote_location: ansible_test state: present register: nm_add_ansibleBackupRemote1 @@ -157,38 +126,26 @@ that: - nm_add_ansibleBackupRemote1.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") -- name: Create ansibleBackupRemote1 in normal mode again - mso_backup: - <<: *mso_info - backup: ansibleBackupRemote1 - description: Remote via Ansible - location_type: remote - remote_location_name: ansible_test - state: present - register: nm_add_ansibleBackupRemote1_again - -- name: Verify nm_add_ansibleBackupRemote1_again +- name: Verify nm_add_ansibleBackupRemote1 assert: that: - - nm_add_ansibleBackupRemote1_again.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") + - nm_add_ansibleBackupRemote1.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") -- name: Create ansibleBackupRemote1 in normal mode again in known path +- name: Create ansibleBackupRemote2 in normal mode in a known path mso_backup: <<: *mso_info - backup: ansibleBackupRemote1 + backup: ansibleBackupRemote2 description: Remote via Ansible location_type: remote - remote_location_name: ansible_test - backup_remote_path: '2' + remote_location: ansible_test + remote_path: 'test' state: present - ignore_errors: yes - register: nm_add_ansibleBackupRemote1_again_known_path + register: nm_add_ansibleBackupRemote2_known_path -- name: Verify nm_add_ansibleBackupRemote1_again_known_path +- name: Verify nm_add_ansibleBackupRemote2_known_path assert: that: - - nm_add_ansibleBackupRemote1_again_known_path.current.backupEntry.metadata.name is match ("ansibleBackupRemote1_[0-9a-zA-Z]*") - ignore_errors: yes + - nm_add_ansibleBackupRemote2_known_path.current.backupEntry.metadata.name is match ("ansibleBackupRemote2_[0-9a-zA-Z]*") # Query a Backup - name: Query ansibleBackup3 @@ -216,87 +173,59 @@ assert: that: - query_all_backups is not changed - - query_all_backups.current | length == 7 + - query_all_backups.current | selectattr("name", "match", "^ansibleBackup.*") | list | length == 6 # Remove Backup -- name: Remove first ansibleBackup3 in check mode +- name: Remove ansibleBackup1 in check mode mso_backup: <<: *mso_info - backup: ansibleBackup3 + backup: ansibleBackup1 state: absent - ignore_errors: yes check_mode: yes - register: rm_b3_cm - -- name: Verify rm_b3_cm - assert: - that: - - rm_b3_cm is changed - -- name: Remove first ansibleBackup3 in normal mode - mso_backup: - <<: *mso_info - backup: ansibleBackup3 - state: absent - ignore_errors: yes - register: nm_remove_ansibleBackup3 + register: cm_remove_ansibleBackup1 -- name: Verify nm_remove_ansibleBackup3 +- name: Verify cm_remove_ansibleBackup1 assert: that: - - nm_remove_ansibleBackup3 is changed + - cm_remove_ansibleBackup1 is changed -- name: Remove second ansibleBackup3 in normal mode +- name: Remove ansibleBackup1 in normal mode mso_backup: <<: *mso_info - backup: ansibleBackup3 + backup: ansibleBackup1 state: absent - ignore_errors: yes - register: nm_remove_ansibleBackup3_2 + register: nm_remove_ansibleBackup1 -- name: Verify nm_remove_ansibleBackup3_2 +- name: Verify nm_remove_ansibleBackup1 assert: that: - - nm_remove_ansibleBackup3_2 is changed + - nm_remove_ansibleBackup1 is changed -- name: Remove ansibleBackup3 again in normal mode +- name: Remove ansibleBackup1 in normal mode again mso_backup: <<: *mso_info - backup: ansibleBackup3 + backup: ansibleBackup1 state: absent - ignore_errors: yes - register: nm_remove_ansibleBackup3_again + register: nm_remove_ansibleBackup1_2 -- name: Verify nm_remove_ansibleBackup3_again +- name: Verify nm_remove_ansibleBackup1_2 assert: that: - - nm_remove_ansibleBackup3_again is not changed + - nm_remove_ansibleBackup1_2 is not changed -- name: Remove first ansibleBackupRemote1 in normal mode - mso_backup: - <<: *mso_info - backup: ansibleBackupRemote1 - state: absent - ignore_errors: yes - register: nm_remove_ansibleBackupRemote1 - -- name: Verify nm_remove_ansibleBackupRemote1 - assert: - that: - - nm_remove_ansibleBackupRemote1 is changed -- name: Remove ansibleBackupRemote1 again in normal mode +- name: Remove ansibleBackup3 in normal mode mso_backup: <<: *mso_info - backup: ansibleBackupRemote1 + backup: ansibleBackup3 state: absent ignore_errors: yes - register: nm_remove_ansibleBackupRemote1_2 + register: nm_remove_ansibleBackup3 -- name: Verify nm_remove_ansibleBackupRemote1_2 +- name: Verify nm_remove_ansibleBackup3 assert: that: - - nm_remove_ansibleBackupRemote1_2 is changed + - nm_remove_ansibleBackup3.msg is match ("Multiple backups with same name found. Existing backups with similar names{{':'}} ansibleBackup3_[0-9]*, ansibleBackup3_[0-9]*") - name: Query non_existent_backup mso_backup: From a353f2ecc52492eea71567da6504a2827cb1efbd Mon Sep 17 00:00:00 2001 From: ssrish Date: Tue, 21 Jul 2020 17:54:49 -0700 Subject: [PATCH 4/4] Reviewed Changes on docs --- plugins/modules/mso_backup.py | 13 ++++++++++++- tests/integration/targets/mso_backup/tasks/main.yml | 9 +-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/plugins/modules/mso_backup.py b/plugins/modules/mso_backup.py index 4611ac0c..9999faf5 100644 --- a/plugins/modules/mso_backup.py +++ b/plugins/modules/mso_backup.py @@ -38,7 +38,8 @@ remote_path: description: - This path is relative to the remote location. - - There's no need to specify '/' for this path. + - A '/' is automatically added between the remote location folder and this path. + - This folder structure should already exist on the remote location. type: str description: description: @@ -97,6 +98,16 @@ delegate_to: localhost register: query_result +- name: Query a backup with its complete name + cisco.mso.mso_backup: + host: mso_host + username: admin + password: SomeSecretPassword + backup: Backup_20200721220043 + state: query + delegate_to: localhost + register: query_result + - name: Query all backups cisco.mso.mso_backup: host: mso_host diff --git a/tests/integration/targets/mso_backup/tasks/main.yml b/tests/integration/targets/mso_backup/tasks/main.yml index 0924976c..6e673e35 100644 --- a/tests/integration/targets/mso_backup/tasks/main.yml +++ b/tests/integration/targets/mso_backup/tasks/main.yml @@ -26,19 +26,13 @@ state: query register: query_ansibleBackup_for_delete -- name: Remove backups with starting with name ansibleBackup +- name: Remove backups starting with name ansibleBackup mso_backup: <<: *mso_info backup: '{{ item.name }}' state: absent loop: '{{ query_ansibleBackup_for_delete.current | selectattr("name", "match", "^ansibleBackup.*") | list }}' -- name: Query all backups post - mso_backup: - <<: *mso_info - state: query - register: query_ansibleBackup_for_post - # Create Backups - name: Create ansibleBackup1 in check mode mso_backup: @@ -213,7 +207,6 @@ that: - nm_remove_ansibleBackup1_2 is not changed - - name: Remove ansibleBackup3 in normal mode mso_backup: <<: *mso_info