summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/network/aos/aos_blueprint_param.py
blob: 7b74b976f636cc39aa969a301663f752f89bcf2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/python
#
# (c) 2017 Apstra Inc, <community@apstra.com>
#
# 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 <http://www.gnu.org/licenses/>.
#

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 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 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.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()