#!/usr/bin/python # # (c) 2017 Apstra Inc, # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: aos_blueprint_param author: jeremy@apstra.com (@jeremyschulman) version_added: "2.3" short_description: Manage AOS blueprint parameter values description: - Apstra AOS Blueprint Parameter module let you manage your Blueprint Parameter easily. You can create access, define and delete Blueprint Parameter. The list of Parameters supported is different per Blueprint. The option I(get_param_list) can help you to access the list of supported Parameters for your blueprint. This module is idempotent and support the I(check) mode. It's using the AOS REST API. requirements: - "aos-pyez >= 0.6.0" options: session: description: - An existing AOS session as obtained by M(aos_login) module. required: true blueprint: description: - Blueprint Name or Id as defined in AOS. required: True name: description: - Name of blueprint parameter, as defined by AOS design template. You can use the option I(get_param_list) to get the complete list of supported parameters for your blueprint. value: description: - Blueprint parameter value. This value may be transformed by using the I(param_map) field; used when the blueprint parameter requires an AOS unique ID value. get_param_list: description: - Get the complete list of supported parameters for this blueprint and the description of those parameters. state: description: - Indicate what is the expected state of the Blueprint Parameter (present or not). default: present choices: ['present', 'absent'] param_map: description: - Defines the aos-pyez collection that will is used to map the user-defined item name into the AOS unique ID value. For example, if the caller provides an IP address pool I(param_value) called "Server-IpAddrs", then the aos-pyez collection is 'IpPools'. Some I(param_map) are already defined by default like I(logical_device_maps). ''' EXAMPLES = ''' - name: Add Logical Device Maps information in a Blueprint aos_blueprint_param: session: "{{ aos_session }}" blueprint: "my-blueprint-l2" name: "logical_device_maps" value: spine_1: CumulusVX-Spine-Switch spine_2: CumulusVX-Spine-Switch leaf_1: CumulusVX-Leaf-Switch leaf_2: CumulusVX-Leaf-Switch leaf_3: CumulusVX-Leaf-Switch state: present - name: Access Logical Device Maps information from a Blueprint aos_blueprint_param: session: "{{ aos_session }}" blueprint: "my-blueprint-l2" name: "logical_device_maps" state: present - name: Reset Logical Device Maps information in a Blueprint aos_blueprint_param: session: "{{ aos_session }}" blueprint: "my-blueprint-l2" name: "logical_device_maps" state: absent - name: Get list of all supported Params for a blueprint aos_blueprint_param: session: "{{ aos_session }}" blueprint: "my-blueprint-l2" get_param_list: yes register: params_list - debug: var=params_list - name: Add Resource Pools information in a Blueprint, by providing a param_map aos_blueprint_param: session: "{{ aos_session }}" blueprint: "my-blueprint-l2" name: "resource_pools" value: leaf_loopback_ips: ['Switches-IpAddrs'] spine_loopback_ips: ['Switches-IpAddrs'] spine_leaf_link_ips: ['Switches-IpAddrs'] spine_asns: ['Private-ASN-pool'] leaf_asns: ['Private-ASN-pool'] virtual_network_svi_subnets: ['Servers-IpAddrs'] param_map: leaf_loopback_ips: IpPools spine_loopback_ips: IpPools spine_leaf_link_ips: IpPools spine_asns: AsnPools leaf_asns: AsnPools virtual_network_svi_subnets: IpPools state: present ''' RETURNS = ''' blueprint: description: Name of the Blueprint returned: always type: str sample: Server-IpAddrs name: description: Name of the Blueprint Parameter returned: always type: str sample: fcc4ac1c-e249-4fe7-b458-2138bfb44c06 value: description: Value of the Blueprint Parameter as returned by the AOS Server returned: always type: dict sample: {'...'} params_list: description: Value of the Blueprint Parameter as returned by the AOS Server returned: when I(get_param_list) is defined. type: dict sample: {'...'} ''' import json from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.network.aos.aos import get_aos_session, find_collection_item, check_aos_version from ansible.module_utils.pycompat24 import get_exception try: import yaml HAS_YAML = True except ImportError: HAS_YAML = False try: from apstra.aosom.collection_mapper import CollectionMapper, MultiCollectionMapper HAS_AOS_PYEZ_MAPPER = True except ImportError: HAS_AOS_PYEZ_MAPPER = False param_map_list = dict( logical_device_maps='LogicalDeviceMaps', resource_pools=dict( spine_asns="AsnPools", leaf_asns="AsnPools", virtual_network_svi_subnets="IpPools", spine_loopback_ips="IpPools", leaf_loopback_ips="IpPools", spine_leaf_link_ips="IpPools" ) ) def get_collection_from_param_map(module, aos): param_map = None # Check if param_map is provided if module.params['param_map'] is not None: param_map_json = module.params['param_map'] if not HAS_YAML: module.fail_json(msg="Python library Yaml is mandatory to use 'param_map'") try: param_map = yaml.safe_load(param_map_json) except: module.fail_json(msg="Unable to parse param_map information") else: # search in the param_map_list to find the right one for key, value in param_map_list.items(): if module.params['name'] == key: param_map = value # If param_map is defined, search for a Collection that matches if param_map: if isinstance(param_map, dict): return MultiCollectionMapper(aos, param_map) else: return CollectionMapper(getattr(aos, param_map)) return None def blueprint_param_present(module, aos, blueprint, param, param_value): margs = module.params # If param_value is not defined, just return the object if not param_value: module.exit_json(changed=False, blueprint=blueprint.name, name=param.name, value=param.value) # Check if current value is the same or not elif param.value != param_value: if not module.check_mode: try: param.value = param_value except: exc = get_exception() module.fail_json(msg='unable to write to param %s: %r' % (margs['name'], exc)) module.exit_json(changed=True, blueprint=blueprint.name, name=param.name, value=param.value) # If value are already the same, nothing needs to be changed else: module.exit_json(changed=False, blueprint=blueprint.name, name=param.name, value=param.value) def blueprint_param_absent(module, aos, blueprint, param, param_value): margs = module.params # Check if current value is the same or not if param.value != dict(): if not module.check_mode: try: param.value = {} except: exc = get_exception() module.fail_json(msg='Unable to write to param %s: %r' % (margs['name'], exc)) module.exit_json(changed=True, blueprint=blueprint.name, name=param.name, value=param.value) else: module.exit_json(changed=False, blueprint=blueprint.name, name=param.name, value=param.value) def blueprint_param(module): margs = module.params # -------------------------------------------------------------------- # Get AOS session object based on Session Info # -------------------------------------------------------------------- try: aos = get_aos_session(module, margs['session']) except: module.fail_json(msg="Unable to login to the AOS server") # -------------------------------------------------------------------- # Get the blueprint Object based on either name or ID # -------------------------------------------------------------------- try: blueprint = find_collection_item(aos.Blueprints, item_name=margs['blueprint'], item_id=margs['blueprint']) except: module.fail_json(msg="Unable to find the Blueprint based on name or ID, something went wrong") if blueprint.exists is False: module.fail_json(msg='Blueprint %s does not exist.\n' 'known blueprints are [%s]' % (margs['blueprint'], ','.join(aos.Blueprints.names))) # -------------------------------------------------------------------- # If get_param_list is defined, build the list of supported parameters # and extract info for each # -------------------------------------------------------------------- if margs['get_param_list']: params_list = {} for param in blueprint.params.names: params_list[param] = blueprint.params[param].info module.exit_json(changed=False, blueprint=blueprint.name, params_list=params_list) # -------------------------------------------------------------------- # Check Param name, return an error if not supported by this blueprint # -------------------------------------------------------------------- if margs['name'] in blueprint.params.names: param = blueprint.params[margs['name']] else: module.fail_json(msg='unable to access param %s' % margs['name']) # -------------------------------------------------------------------- # Check if param_value needs to be converted to an object # based on param_map # -------------------------------------------------------------------- param_value = margs['value'] param_collection = get_collection_from_param_map(module, aos) # If a collection is find and param_value is defined, # convert param_value into an object if param_collection and param_value: param_value = param_collection.from_label(param_value) # -------------------------------------------------------------------- # Proceed based on State value # -------------------------------------------------------------------- if margs['state'] == 'absent': blueprint_param_absent(module, aos, blueprint, param, param_value) elif margs['state'] == 'present': blueprint_param_present(module, aos, blueprint, param, param_value) def main(): module = AnsibleModule( argument_spec=dict( session=dict(required=True, type="dict"), blueprint=dict(required=True), get_param_list=dict(required=False, type="bool"), name=dict(required=False), value=dict(required=False, type="dict"), param_map=dict(required=False), state=dict(choices=['present', 'absent'], default='present') ), supports_check_mode=True ) # Check if aos-pyez is present and match the minimum version check_aos_version(module, '0.6.0') # aos-pyez availability has been verify already by "check_aos_version" # but this module requires few more object if not HAS_AOS_PYEZ_MAPPER: module.fail_json(msg='unable to load the Mapper library from aos-pyez') blueprint_param(module) if __name__ == '__main__': main()