diff --git a/lib/ansible/modules/network/aos/aos_blueprint_param.py b/lib/ansible/modules/network/aos/aos_blueprint_param.py new file mode 100644 index 0000000000..45e9202d79 --- /dev/null +++ b/lib/ansible/modules/network/aos/aos_blueprint_param.py @@ -0,0 +1,378 @@ +#!/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 = {'status': ['preview'], + 'supported_by': 'community', + 'version': '1.0'} + +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 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 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: "{{session_ok}}" + 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: "{{session_ok}}" + blueprint: "my-blueprint-l2" + name: "logical_device_maps" + state: present + +- name: Reset Logical Device Maps information in a Blueprint + aos_blueprint_param: + session: "{{session_ok}}" + blueprint: "my-blueprint-l2" + name: "logical_device_maps" + state: absent + +- name: Get list of all supported Params for a blueprint + aos_blueprint_param: + session: "{{session_ok}}" + 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: "{{session_ok}}" + 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.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.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()