#!/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': 'certified'} DOCUMENTATION = r''' --- module: aci_config_rollback short_description: Provides rollback and rollback preview functionality (config:ImportP) description: - Provides rollback and rollback preview functionality for Cisco ACI fabrics. - Config Rollbacks are done using snapshots C(aci_snapshot) with the configImportP class. notes: - More information about the internal APIC class B(config:ImportP) from L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). author: - Jacob McGill (@jmcgill298) version_added: '2.4' options: compare_export_policy: description: - The export policy that the C(compare_snapshot) is associated to. compare_snapshot: description: - The name of the snapshot to compare with C(snapshot). description: description: - The description for the Import Policy. aliases: [ descr ] export_policy: description: - The export policy that the C(snapshot) is associated to. required: yes fail_on_decrypt: description: - Determines if the APIC should fail the rollback if unable to decrypt secured data. - The APIC defaults to C(yes) when unset. type: bool import_mode: description: - Determines how the import should be handled by the APIC. - The APIC defaults to C(atomic) when unset. choices: [ atomic, best-effort ] import_policy: description: - The name of the Import Policy to use for config rollback. import_type: description: - Determines how the current and snapshot configuration should be compared for replacement. - The APIC defaults to C(replace) when unset. choices: [ merge, replace ] snapshot: description: - The name of the snapshot to rollback to, or the base snapshot to use for comparison. - The C(aci_snapshot) module can be used to query the list of available snapshots. required: yes state: description: - Use C(preview) for previewing the diff between two snapshots. - Use C(rollback) for reverting the configuration to a previous snapshot. choices: [ preview, rollback ] default: rollback extends_documentation_fragment: aci ''' EXAMPLES = r''' --- - name: Create a Snapshot aci_config_snapshot: host: apic username: admin password: SomeSecretPassword export_policy: config_backup state: present delegate_to: localhost - name: Query Existing Snapshots aci_config_snapshot: host: apic username: admin password: SomeSecretPassword export_policy: config_backup state: query delegate_to: localhost - name: Compare Snapshot Files aci_config_rollback: host: apic username: admin password: SomeSecretPassword export_policy: config_backup snapshot: run-2017-08-28T06-24-01 compare_export_policy: config_backup compare_snapshot: run-2017-08-27T23-43-56 state: preview delegate_to: localhost - name: Rollback Configuration aci_config_rollback: host: apic username: admin password: SomeSecretPassword import_policy: rollback_config export_policy: config_backup snapshot: run-2017-08-28T06-24-01 state: rollback delegate_to: localhost - name: Rollback Configuration aci_config_rollback: host: apic username: admin password: SomeSecretPassword import_policy: rollback_config export_policy: config_backup snapshot: run-2017-08-28T06-24-01 description: Rollback 8-27 changes import_mode: atomic import_type: replace fail_on_decrypt: yes state: rollback delegate_to: localhost ''' RETURN = r''' preview: description: A preview between two snapshots returned: when state is preview type: string error: description: The error information as returned from the APIC returned: failure type: dict sample: { "code": "122", "text": "unknown managed object class foo" } raw: description: The raw output returned by the APIC REST API (xml or json) returned: parse error type: string sample: '' filter_string: description: The filter string used for the request returned: failure or debug type: string sample: ?rsp-prop-include=config-only method: description: The HTTP method used for the request to the APIC returned: failure or debug type: string sample: POST response: description: The HTTP response from the APIC returned: failure or debug type: string sample: OK (30 bytes) status: description: The HTTP status from the APIC returned: failure or debug type: int sample: 200 url: description: The HTTP url used for the request to the APIC returned: failure or debug type: string sample: https://10.11.12.13/api/mo/uni/tn-production.json ''' from ansible.module_utils.network.aci.aci import ACIModule, aci_argument_spec from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_bytes from ansible.module_utils.urls import fetch_url # Optional, only used for rollback preview try: import lxml.etree from xmljson import cobra XML_TO_JSON = True except ImportError: XML_TO_JSON = False def main(): argument_spec = aci_argument_spec() argument_spec.update( compare_export_policy=dict(type='str'), compare_snapshot=dict(type='str'), description=dict(type='str', aliases=['descr']), export_policy=dict(type='str'), fail_on_decrypt=dict(type='bool'), import_mode=dict(type='str', choices=['atomic', 'best-effort']), import_policy=dict(type='str'), import_type=dict(type='str', choices=['merge', 'replace']), snapshot=dict(type='str', required=True), state=dict(type='str', default='rollback', choices=['preview', 'rollback']), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=False, required_if=[ ['state', 'preview', ['compare_export_policy', 'compare_snapshot']], ['state', 'rollback', ['import_policy']], ], ) aci = ACIModule(module) description = module.params['description'] export_policy = module.params['export_policy'] fail_on_decrypt = aci.boolean(module.params['fail_on_decrypt']) import_mode = module.params['import_mode'] import_policy = module.params['import_policy'] import_type = module.params['import_type'] snapshot = module.params['snapshot'] state = module.params['state'] if state == 'rollback': if snapshot.startswith('run-'): snapshot = snapshot.replace('run-', '', 1) if not snapshot.endswith('.tar.gz'): snapshot += '.tar.gz' filename = 'ce2_{0}-{1}'.format(export_policy, snapshot) aci.construct_url( root_class=dict( aci_class='configImportP', aci_rn='fabric/configimp-{0}'.format(import_policy), module_object=import_policy, target_filter={'name': import_policy}, ), ) aci.get_existing() aci.payload( aci_class='configImportP', class_config=dict( adminSt='triggered', descr=description, failOnDecryptErrors=fail_on_decrypt, fileName=filename, importMode=import_mode, importType=import_type, name=import_policy, snapshot='yes', ), ) aci.get_diff(aci_class='configImportP') aci.post_config() elif state == 'preview': aci.url = '%(protocol)s://%(host)s/mqapi2/snapshots.diff.xml' % module.params aci.filter_string = ( '?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-%(export_policy)s]/snapshot-%(snapshot)s&' 's2dn=uni/backupst/snapshots-[uni/fabric/configexp-%(compare_export_policy)s]/snapshot-%(compare_snapshot)s' ) % module.params # Generate rollback comparison get_preview(aci) aci.exit_json() def get_preview(aci): ''' This function is used to generate a preview between two snapshots and add the parsed results to the aci module return data. ''' uri = aci.url + aci.filter_string resp, info = fetch_url(aci.module, uri, headers=aci.headers, method='GET', timeout=aci.module.params['timeout'], use_proxy=aci.module.params['use_proxy']) aci.method = 'GET' aci.response = info['msg'] aci.status = info['status'] # Handle APIC response if info['status'] == 200: xml_to_json(aci, resp.read()) else: aci.result['raw'] = resp.read() aci.fail_json(msg="Request failed: %(code)s %(text)s (see 'raw' output)" % aci.error) def xml_to_json(aci, response_data): ''' This function is used to convert preview XML data into JSON. ''' if XML_TO_JSON: xml = lxml.etree.fromstring(to_bytes(response_data)) xmldata = cobra.data(xml) aci.result['preview'] = xmldata else: aci.result['preview'] = response_data if __name__ == "__main__": main()