From 42db02c249eba2fdabb44d7d580d2aa1dbbb9a80 Mon Sep 17 00:00:00 2001 From: Jacob McGill Date: Wed, 30 Aug 2017 00:31:14 -0400 Subject: [PATCH] aci_config_snapshot: Module to manage ACI config snapshots (#28784) * ACI Config Snapshot: Add module to support managing config snapshots on the APIC. * add comma back * Fix docstring errors * Fix typo * add quotes for notes section --- lib/ansible/module_utils/aci.py | 3 + .../network/aci/aci_config_snapshot.py | 211 ++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 lib/ansible/modules/network/aci/aci_config_snapshot.py diff --git a/lib/ansible/module_utils/aci.py b/lib/ansible/module_utils/aci.py index 267b5342c7..cbf2e05f24 100644 --- a/lib/ansible/module_utils/aci.py +++ b/lib/ansible/module_utils/aci.py @@ -74,6 +74,7 @@ URL_MAPPING = dict( epg_domain=dict(aci_class='fvRsDomAtt', mo='rsdomAtt-', key='tDn'), epg_provider=dict(aci_class='fvRsProv', mo='rsprov-', key='tnVzBrCPName'), epr_policy=dict(aci_class='fvEpRetPol', mo='epRPol-', key='name'), + export_policy=dict(aci_class='configExportP', mo='fabric/configexp-', key='name'), fc_policy=dict(aci_class='fcIfPol', mo='infra/fcIfPol-', key='name'), filter=dict(aci_class='vzFilter', mo='flt-', key='name'), gateway_addr=dict(aci_class='fvSubnet', mo='subnet-', key='ip'), @@ -84,6 +85,8 @@ URL_MAPPING = dict( port_channel=dict(aci_class='lacpLagPol', mo='infra/lacplagp-', key='name'), port_security=dict(aci_class='l2PortSecurityPol', mo='infra/portsecurityP-', key='name'), rtp=dict(aci_class='l3extRouteTagPol', mo='rttag-', key='name'), + snapshot=dict(aci_class='configSnapshot', mo='snapshot-', key='name'), + snapshot_container=dict(aci_class='configSnapshotCont', mo='backupst/snapshots-', key='name'), subject=dict(aci_class='vzSubj', mo='subj-', key='name'), subject_filter=dict(aci_class='vzRsSubjFiltAtt', mo='rssubjFiltAtt-', key='tnVzFilterName'), taboo_contract=dict(aci_class='vzTaboo', mo='taboo-', key='name'), diff --git a/lib/ansible/modules/network/aci/aci_config_snapshot.py b/lib/ansible/modules/network/aci/aci_config_snapshot.py new file mode 100644 index 0000000000..db81de3efc --- /dev/null +++ b/lib/ansible/modules/network/aci/aci_config_snapshot.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: aci_config_snapshot +short_description: Manage Config Snapshots on Cisco ACI fabrics (config:Snapshot, config:ExportP) +description: +- Manage Config Snapshots on Cisco ACI fabrics. +- Creating new Snapshots is done using the configExportP class. +- Removing Snapshots is done using the configSnapshot class. +- More information from the internal APIC classes + I(config:Snapshot) at U(https://developer.cisco.com/media/mim-ref/MO-configSnapshot.html) and + I(config:ExportP) at U(https://developer.cisco.com/media/mim-ref/MO-configExportP.html). +author: +- Swetha Chunduri (@schunduri) +- Dag Wieers (@dagwieers) +- Jacob McGill (@jmcgill298) +version_added: '2.4' +requirements: +- Tested with ACI Fabric 1.0(3f)+ +notes: +- The APIC does not provide a mechanism for naming the snapshots. +- 'Snapshot files use the following naming structure: ce_---
T::.+:.' +- 'Snapshot objects use the following naming structure: run---
T--.' +options: + description: + description: + - The description for the Config Export Policy. + aliases: [ descr ] + export_policy: + description: + - The name of the Export Policy to use for Config Snapshots. + aliases: [ name ] + format: + description: + - Sets the config backup to be formatted in JSON or XML. + - The APIC defaults new Export Policies to C(json) + choices: [ json, xml ] + default: json + include_secure: + description: + - Determines if secure information should be included in the backup. + - The APIC defaults new Export Policies to C(yes). + choices: [ 'no', 'yes' ] + default: 'yes' + max_count: + description: + - Determines how many snapshots can exist for the Export Policy before the APIC starts to rollover. + - The APIC defaults new Export Policies to C(3). + choices: [ range between 1 and 10 ] + default: 3 + snapshot: + description: + - The name of the snapshot to delete. + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: aci +''' + +EXAMPLES = r''' +- name: Create a Snapshot + aci_config_snapshot: + hostname: apic + username: admin + password: SomeSecretPassword + state: present + export_policy: config_backup + max_count: 10 + description: Backups taken before new configs are applied. + +- name: Query all Snapshots + aci_config_snapshot: + hostname: apic + username: admin + password: SomeSecretPassword + state: query + +- name: Query Snapshots associated with a particular Export Policy + aci_config_snapshot: + hostname: apic + username: admin + password: SomeSecretPassword + state: query + export_policy: config_backup + +- name: Delete a Snapshot + aci_config_snapshot: + hostname: apic + username: admin + password: SomeSecretPassword + state: absent + export_policy: config_backup + snapshot: run-2017-08-24T17-20-05 +''' + +RETURN = r''' # ''' + +from ansible.module_utils.aci import ACIModule, aci_argument_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec + argument_spec.update( + description=dict(type='str', aliases=['descr']), + export_policy=dict(type='str', aliases=['name']), + format=dict(type='str', choices=['json', 'xml']), + include_secure=dict(type='str', choices=['no', 'yes']), + max_count=dict(type='int'), + snapshot=dict(type='str'), + state=dict(type='str', choices=['absent', 'present', 'query'], default='present'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + required_if=[ + ['state', 'absent', ['export_policy', 'snapshot']], + ['state', 'present', ['export_policy']], + ], + ) + + description = module.params['description'] + export_policy = module.params['export_policy'] + file_format = module.params['format'] + include_secure = module.params['include_secure'] + max_count = module.params['max_count'] + if max_count is not None: + if max_count in range(1, 11): + max_count = str(max_count) + else: + module.fail_json(msg='The "max_count" must be a number between 1 and 10') + snapshot = module.params['snapshot'] + if snapshot is not None and not snapshot.startswith('run-'): + snapshot = 'run-' + snapshot + module.params['snapshot'] = snapshot + state = module.params['state'] + + aci = ACIModule(module) + + if state == 'present': + aci.construct_url(root_class='export_policy') + aci.get_existing() + + # Filter out module params with null values + aci.payload( + aci_class='configExportP', + class_config=dict( + adminSt='triggered', + descr=description, + format=file_format, + includeSecureFields=include_secure, + maxSnapshotCount=max_count, + name=export_policy, + snapshot='yes', + ), + ) + + aci.get_diff('configExportP') + + # Create a new Snapshot + aci.post_config() + + else: + # Add snapshot_container to module.params to build URL + if export_policy is not None: + module.params['snapshot_container'] = 'uni/fabric/configexp-{}'.format(module.params['export_policy']) + else: + module.params['snapshot_container'] = None + + aci.construct_url(root_class='snapshot_container', subclass_1='snapshot') + aci.get_existing() + + if state == 'absent': + # Build POST request to used to remove Snapshot + aci.payload( + aci_class='configSnapshot', + class_config=dict( + name=snapshot, + retire="yes", + ), + ) + + if aci.result['existing']: + aci.get_diff('configSnapshot') + + # Mark Snapshot for Deletion + aci.post_config() + + # Remove snapshot used to build URL from module.params + module.params.pop('snapshot_container') + + module.exit_json(**aci.result) + + +if __name__ == "__main__": + main()