summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/cisco/mso/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'collections-debian-merged/ansible_collections/cisco/mso/plugins')
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py85
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/module_utils/mso.py949
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_backup.py311
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py167
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py193
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py166
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py254
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_label.py165
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_rest.py215
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_role.py277
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema.py133
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py200
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py214
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py230
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py474
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py398
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py264
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py448
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py293
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py242
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py224
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py301
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py295
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py213
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py239
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py317
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py302
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py251
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py244
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py207
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py403
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py266
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py281
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py266
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py444
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py253
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py352
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py143
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py332
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py250
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py249
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py220
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py332
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py363
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py231
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py246
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py214
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py264
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_site.py252
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py197
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py391
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_user.py277
-rw-r--r--collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_version.py71
53 files changed, 14568 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py
new file mode 100644
index 00000000..b4d9924d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/doc_fragments/modules.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+class ModuleDocFragment(object):
+ # Standard files documentation fragment
+ DOCUMENTATION = r'''
+options:
+ host:
+ description:
+ - IP Address or hostname of the ACI Multi Site Orchestrator host.
+ - If the value is not specified in the task, the value of environment variable C(MSO_HOST) will be used instead.
+ type: str
+ required: yes
+ aliases: [ hostname ]
+ port:
+ description:
+ - Port number to be used for the REST connection.
+ - The default value depends on parameter `use_ssl`.
+ - If the value is not specified in the task, the value of environment variable C(MSO_PORT) will be used instead.
+ type: int
+ username:
+ description:
+ - The username to use for authentication.
+ - If the value is not specified in the task, the value of environment variables C(MSO_USERNAME) or C(ANSIBLE_NET_USERNAME) will be used instead.
+ type: str
+ default: admin
+ password:
+ description:
+ - The password to use for authentication.
+ - If the value is not specified in the task, the value of environment variables C(MSO_PASSWORD) or C(ANSIBLE_NET_PASSWORD) will be used instead.
+ type: str
+ required: yes
+ output_level:
+ description:
+ - Influence the output of this ACI module.
+ - C(normal) means the standard output, incl. C(current) dict
+ - C(info) adds informational output, incl. C(previous), C(proposed) and C(sent) dicts
+ - C(debug) adds debugging output, incl. C(filter_string), C(method), C(response), C(status) and C(url) information
+ - If the value is not specified in the task, the value of environment variable C(MSO_OUTPUT_LEVEL) will be used instead.
+ type: str
+ choices: [ debug, info, normal ]
+ default: normal
+ timeout:
+ description:
+ - The socket level timeout in seconds.
+ - If the value is not specified in the task, the value of environment variable C(MSO_TIMEOUT) will be used instead.
+ type: int
+ default: 30
+ use_proxy:
+ description:
+ - If C(no), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
+ - If the value is not specified in the task, the value of environment variable C(MSO_USE_PROXY) will be used instead.
+ type: bool
+ default: yes
+ use_ssl:
+ description:
+ - If C(no), an HTTP connection will be used instead of the default HTTPS connection.
+ - If the value is not specified in the task, the value of environment variable C(MSO_USE_SSL) will be used instead.
+ type: bool
+ default: yes
+ validate_certs:
+ description:
+ - If C(no), SSL certificates will not be validated.
+ - This should only set to C(no) when used on personally controlled sites using self-signed certificates.
+ - If the value is not specified in the task, the value of environment variable C(MSO_VALIDATE_CERTS) will be used instead.
+ type: bool
+ default: yes
+ login_domain:
+ description:
+ - The login domain name to use for authentication.
+ - The default value is Local.
+ - If the value is not specified in the task, the value of environment variable C(MSO_LOGIN_DOMAIN) will be used instead.
+ type: str
+requirements:
+- Multi Site Orchestrator v2.1 or newer
+notes:
+- Please read the :ref:`aci_guide` for more detailed information on how to manage your ACI infrastructure using Ansible.
+- This module was written to support ACI Multi Site Orchestrator v2.1 or newer. Some or all functionality may not work on earlier versions.
+'''
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/module_utils/mso.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/module_utils/mso.py
new file mode 100644
index 00000000..91485df0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/module_utils/mso.py
@@ -0,0 +1,949 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+from copy import deepcopy
+import re
+import os
+import datetime
+import shutil
+import tempfile
+from ansible.module_utils.basic import json
+from ansible.module_utils.basic import env_fallback
+from ansible.module_utils.six import PY3
+from ansible.module_utils.six.moves import filterfalse
+from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin
+from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_native
+try:
+ from requests_toolbelt.multipart.encoder import MultipartEncoder
+ HAS_MULTIPART_ENCODER = True
+except ImportError:
+ HAS_MULTIPART_ENCODER = False
+
+
+if PY3:
+ def cmp(a, b):
+ return (a > b) - (a < b)
+
+
+def issubset(subset, superset):
+ ''' Recurse through nested dictionary and compare entries '''
+
+ # Both objects are the same object
+ if subset is superset:
+ return True
+
+ # Both objects are identical
+ if subset == superset:
+ return True
+
+ # Both objects have a different type
+ if type(subset) != type(superset):
+ return False
+
+ for key, value in subset.items():
+ # Ignore empty values
+ if value is None:
+ return True
+
+ # Item from subset is missing from superset
+ if key not in superset:
+ return False
+
+ # Item has different types in subset and superset
+ if type(superset.get(key)) != type(value):
+ return False
+
+ # Compare if item values are subset
+ if isinstance(value, dict):
+ if not issubset(superset.get(key), value):
+ return False
+ elif isinstance(value, list):
+ try:
+ # NOTE: Fails for lists of dicts
+ if not set(value) <= set(superset.get(key)):
+ return False
+ except TypeError:
+ # Fall back to exact comparison for lists of dicts
+ diff = list(filterfalse(lambda i: i in value, superset.get(key))) + list(filterfalse(lambda j: j in superset.get(key), value))
+ if diff:
+ return False
+ elif isinstance(value, set):
+ if not value <= superset.get(key):
+ return False
+ else:
+ if not value == superset.get(key):
+ return False
+
+ return True
+
+
+def update_qs(params):
+ ''' Append key-value pairs to self.filter_string '''
+ accepted_params = dict((k, v) for (k, v) in params.items() if v is not None)
+ return '?' + urlencode(accepted_params)
+
+
+def mso_argument_spec():
+ return dict(
+ host=dict(type='str', required=True, aliases=['hostname'], fallback=(env_fallback, ['MSO_HOST'])),
+ port=dict(type='int', required=False, fallback=(env_fallback, ['MSO_PORT'])),
+ username=dict(type='str', default='admin', fallback=(env_fallback, ['MSO_USERNAME', 'ANSIBLE_NET_USERNAME'])),
+ password=dict(type='str', required=True, no_log=True, fallback=(env_fallback, ['MSO_PASSWORD', 'ANSIBLE_NET_PASSWORD'])),
+ output_level=dict(type='str', default='normal', choices=['debug', 'info', 'normal'], fallback=(env_fallback, ['MSO_OUTPUT_LEVEL'])),
+ timeout=dict(type='int', default=30, fallback=(env_fallback, ['MSO_TIMEOUT'])),
+ use_proxy=dict(type='bool', default=True, fallback=(env_fallback, ['MSO_USE_PROXY'])),
+ use_ssl=dict(type='bool', default=True, fallback=(env_fallback, ['MSO_USE_SSL'])),
+ validate_certs=dict(type='bool', default=True, fallback=(env_fallback, ['MSO_VALIDATE_CERTS'])),
+ login_domain=dict(type='str', fallback=(env_fallback, ['MSO_LOGIN_DOMAIN'])),
+ )
+
+
+def mso_reference_spec():
+ return dict(
+ name=dict(type='str', required=True),
+ schema=dict(type='str'),
+ template=dict(type='str'),
+ )
+
+
+def mso_subnet_spec():
+ return dict(
+ subnet=dict(type='str', required=True, aliases=['ip']),
+ description=dict(type='str'),
+ scope=dict(type='str', default='private', choices=['private', 'public']),
+ shared=dict(type='bool', default=False),
+ no_default_gateway=dict(type='bool', default=False),
+ querier=dict(type='bool', default=False),
+ )
+
+
+def mso_dhcp_spec():
+ return dict(
+ dhcp_option_policy=dict(type='dict', option=mso_dhcp_option_spec()),
+ name=dict(type='str', required=True),
+ version=dict(type='int', required=True),
+ )
+
+
+def mso_dhcp_option_spec():
+ return dict(
+ name=dict(type='str', required=True),
+ version=dict(type='int', required=True),
+ )
+
+
+def mso_contractref_spec():
+ return dict(
+ name=dict(type='str', required=True),
+ schema=dict(type='str'),
+ template=dict(type='str'),
+ type=dict(type='str', required=True, choices=['consumer', 'provider']),
+ )
+
+
+def mso_expression_spec():
+ return dict(
+ type=dict(type='str', required=True, aliases=['tag']),
+ operator=dict(type='str', choices=['not_in', 'in', 'equals', 'not_equals', 'has_key', 'does_not_have_key'], required=True),
+ value=dict(type='str'),
+ )
+
+
+def mso_expression_spec_ext_epg():
+ return dict(
+ type=dict(type='str', choices=['ip_address'], required=True),
+ operator=dict(type='str', choices=['equals'], required=True),
+ value=dict(type='str', required=True),
+ )
+
+
+def mso_hub_network_spec():
+ return dict(
+ name=dict(type='str', required=True),
+ tenant=dict(type='str', required=True),
+ )
+
+
+def mso_object_migrate_spec():
+ return dict(
+ epg=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ )
+
+
+# Copied from ansible's module uri.py (url): https://github.com/ansible/ansible/blob/cdf62edc65f564fff6b7e575e084026fa7faa409/lib/ansible/modules/uri.py
+def write_file(module, url, dest, content, resp):
+ # create a tempfile with some test content
+ fd, tmpsrc = tempfile.mkstemp(dir=module.tmpdir)
+ f = open(tmpsrc, 'wb')
+ try:
+ f.write(content)
+ except Exception as e:
+ os.remove(tmpsrc)
+ module.fail_json(msg="Failed to create temporary content file: {0}".format(to_native(e)))
+ f.close()
+
+ checksum_src = None
+ checksum_dest = None
+
+ # raise an error if there is no tmpsrc file
+ if not os.path.exists(tmpsrc):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Source '{0}' does not exist".format(tmpsrc))
+ if not os.access(tmpsrc, os.R_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Source '{0}' is not readable".format(tmpsrc))
+ checksum_src = module.sha1(tmpsrc)
+
+ # check if there is no dest file
+ if os.path.exists(dest):
+ # raise an error if copy has no permission on dest
+ if not os.access(dest, os.W_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination '{0}' not writable".format(dest))
+ if not os.access(dest, os.R_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination '{0}' not readable".format(dest))
+ checksum_dest = module.sha1(dest)
+ else:
+ if not os.access(os.path.dirname(dest), os.W_OK):
+ os.remove(tmpsrc)
+ module.fail_json(msg="Destination dir '{0}' not writable".format(os.path.dirname(dest)))
+
+ if checksum_src != checksum_dest:
+ try:
+ shutil.copyfile(tmpsrc, dest)
+ except Exception as e:
+ os.remove(tmpsrc)
+ module.fail_json(msg="failed to copy {0} to {1}: {2}".format(tmpsrc, dest, to_native(e)))
+
+ os.remove(tmpsrc)
+
+
+class MSOModule(object):
+
+ def __init__(self, module):
+ self.module = module
+ self.params = module.params
+ self.result = dict(changed=False)
+ self.headers = {'Content-Type': 'text/json'}
+
+ # normal output
+ self.existing = dict()
+
+ # mso_rest output
+ self.jsondata = None
+ self.error = dict(code=None, message=None, info=None)
+
+ # info output
+ self.previous = dict()
+ self.proposed = dict()
+ self.sent = dict()
+ self.stdout = None
+
+ # debug output
+ self.has_modified = False
+ self.filter_string = ''
+ self.method = None
+ self.path = None
+ self.response = None
+ self.status = None
+ self.url = None
+
+ # Ensure protocol is set
+ self.params['protocol'] = 'https' if self.params.get('use_ssl', True) else 'http'
+
+ # Set base_uri
+ if self.params.get('port') is not None:
+ self.baseuri = '{protocol}://{host}:{port}/api/v1/'.format(**self.params)
+ else:
+ self.baseuri = '{protocol}://{host}/api/v1/'.format(**self.params)
+
+ if self.module._debug:
+ self.module.warn('Enable debug output because ANSIBLE_DEBUG was set.')
+ self.params['output_level'] = 'debug'
+
+ if self.params.get('password'):
+ # Perform password-based authentication, log on using password
+ self.login()
+ else:
+ self.module.fail_json(msg="Parameter 'password' is required for authentication")
+
+ def get_login_domain_id(self, domain):
+ ''' Get a domain and return its id '''
+ if domain is None:
+ return domain
+ d = self.get_obj('auth/login-domains', key='domains', name=domain)
+ if not d:
+ self.module.fail_json(msg="Login domain '%s' is not a valid domain name." % domain)
+ if 'id' not in d:
+ self.module.fail_json(msg="Login domain lookup failed for domain '%s': %s" % (domain, d))
+ return d['id']
+
+ def login(self):
+ ''' Log in to MSO '''
+
+ # Perform login request
+ if (self.params.get('login_domain') is not None) and (self.params.get('login_domain') != 'Local'):
+ domain_id = self.get_login_domain_id(self.params.get('login_domain'))
+ payload = {'username': self.params.get('username'), 'password': self.params.get('password'), 'domainId': domain_id}
+ else:
+ payload = {'username': self.params.get('username'), 'password': self.params.get('password')}
+ self.url = urljoin(self.baseuri, 'auth/login')
+ resp, auth = fetch_url(self.module,
+ self.url,
+ data=json.dumps(payload),
+ method='POST',
+ headers=self.headers,
+ timeout=self.params.get('timeout'),
+ use_proxy=self.params.get('use_proxy'))
+
+ # Handle MSO response
+ if auth.get('status') != 201:
+ self.response = auth.get('msg')
+ self.status = auth.get('status')
+ self.fail_json(msg='Authentication failed: {msg}'.format(**auth))
+
+ payload = json.loads(resp.read())
+
+ self.headers['Authorization'] = 'Bearer {token}'.format(**payload)
+
+ def response_json(self, rawoutput):
+ ''' Handle MSO JSON response output '''
+ try:
+ self.jsondata = json.loads(rawoutput)
+ except Exception as e:
+ # Expose RAW output for troubleshooting
+ self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e)
+ self.result['raw'] = rawoutput
+ return
+
+ # Handle possible MSO error information
+ if self.status not in [200, 201, 202, 204]:
+ self.error = self.jsondata
+
+ def request_download(self, path, destination=None):
+ self.url = urljoin(self.baseuri, path)
+ redirected = False
+ redir_info = {}
+ redirect = {}
+
+ src = self.params.get('src')
+ if src:
+ try:
+ self.headers.update({
+ 'Content-Length': os.stat(src).st_size
+ })
+ data = open(src, 'rb')
+ except OSError:
+ self.module.fail_json(msg='Unable to open source file %s' % src, elapsed=0)
+ else:
+ pass
+
+ data = None
+
+ kwargs = {}
+ if destination is not None:
+ if os.path.isdir(destination):
+ # first check if we are redirected to a file download
+ check, redir_info = fetch_url(self.module, self.url,
+ headers=self.headers,
+ method='GET',
+ timeout=self.params.get('timeout'))
+ # if we are redirected, update the url with the location header,
+ # and update dest with the new url filename
+ if redir_info['status'] in (301, 302, 303, 307):
+ self.url = redir_info.get('location')
+ redirected = True
+ destination = os.path.join(destination, check.headers.get("Content-Disposition").split("filename=")[1])
+ # if destination file already exist, only download if file newer
+ if os.path.exists(destination):
+ kwargs['last_mod_time'] = datetime.datetime.utcfromtimestamp(os.path.getmtime(destination))
+
+ resp, info = fetch_url(self.module, self.url, data=data, headers=self.headers,
+ method='GET', timeout=self.params.get('timeout'), unix_socket=self.params.get('unix_socket'), **kwargs)
+
+ try:
+ content = resp.read()
+ except AttributeError:
+ # there was no content, but the error read() may have been stored in the info as 'body'
+ content = info.pop('body', '')
+
+ if src:
+ # Try to close the open file handle
+ try:
+ data.close()
+ except Exception:
+ pass
+
+ redirect['redirected'] = redirected or info.get('url') != self.url
+ redirect.update(redir_info)
+ redirect.update(info)
+
+ write_file(self.module, self.url, destination, content, redirect)
+
+ return redirect, destination
+
+ def request_upload(self, path, fields=None):
+ ''' Generic HTTP MultiPart POST method for MSO uploads. '''
+ self.path = path
+ self.url = urljoin(self.baseuri, path)
+
+ if not HAS_MULTIPART_ENCODER:
+ self.fail_json(msg='requests-toolbelt is required for the upload state of this module')
+
+ mp_encoder = MultipartEncoder(fields=fields)
+ self.headers['Content-Type'] = mp_encoder.content_type
+ self.headers['Accept-Encoding'] = "gzip, deflate, br"
+
+ resp, info = fetch_url(self.module,
+ self.url,
+ headers=self.headers,
+ data=mp_encoder,
+ method='POST',
+ timeout=self.params.get('timeout'),
+ use_proxy=self.params.get('use_proxy'))
+
+ self.response = info.get('msg')
+ self.status = info.get('status')
+
+ # Get change status from HTTP headers
+ if 'modified' in info:
+ self.has_modified = True
+ if info.get('modified') == 'false':
+ self.result['changed'] = False
+ elif info.get('modified') == 'true':
+ self.result['changed'] = True
+
+ # 200: OK, 201: Created, 202: Accepted, 204: No Content
+ if self.status in (200, 201, 202, 204):
+ output = resp.read()
+ if output:
+ return json.loads(output)
+
+ # 400: Bad Request, 401: Unauthorized, 403: Forbidden,
+ # 405: Method Not Allowed, 406: Not Acceptable
+ # 500: Internal Server Error, 501: Not Implemented
+ elif self.status >= 400:
+ try:
+ payload = json.loads(resp.read())
+ except (ValueError, AttributeError):
+ try:
+ payload = json.loads(info.get('body'))
+ except Exception:
+ self.fail_json(msg='MSO Error:', info=info)
+ if 'code' in payload:
+ self.fail_json(msg='MSO Error {code}: {message}'.format(**payload), info=info, payload=payload)
+ else:
+ self.fail_json(msg='MSO Error:'.format(**payload), info=info, payload=payload)
+
+ return {}
+
+ def request(self, path, method=None, data=None, qs=None):
+ ''' Generic HTTP method for MSO requests. '''
+ self.path = path
+
+ if method is not None:
+ self.method = method
+
+ # If we PATCH with empty operations, return
+ if method == 'PATCH' and not data:
+ return {}
+
+ self.url = urljoin(self.baseuri, path)
+
+ if qs is not None:
+ self.url = self.url + update_qs(qs)
+
+ resp, info = fetch_url(self.module,
+ self.url,
+ headers=self.headers,
+ data=json.dumps(data),
+ method=self.method,
+ timeout=self.params.get('timeout'),
+ use_proxy=self.params.get('use_proxy'))
+
+ self.response = info.get('msg')
+ self.status = info.get('status')
+
+ # self.result['info'] = info
+
+ # Get change status from HTTP headers
+ if 'modified' in info:
+ self.has_modified = True
+ if info.get('modified') == 'false':
+ self.result['changed'] = False
+ elif info.get('modified') == 'true':
+ self.result['changed'] = True
+
+ # 200: OK, 201: Created, 202: Accepted, 204: No Content
+ if self.status in (200, 201, 202, 204):
+ output = resp.read()
+ if output:
+ return json.loads(output)
+
+ # 404: Not Found
+ elif self.method == 'DELETE' and self.status == 404:
+ return {}
+
+ # 400: Bad Request, 401: Unauthorized, 403: Forbidden,
+ # 405: Method Not Allowed, 406: Not Acceptable
+ # 500: Internal Server Error, 501: Not Implemented
+ elif self.status >= 400:
+ try:
+ output = resp.read()
+ payload = json.loads(output)
+ except (ValueError, AttributeError):
+ try:
+ payload = json.loads(info.get('body'))
+ except Exception:
+ self.fail_json(msg='MSO Error:', data=data, info=info)
+ if 'code' in payload:
+ self.fail_json(msg='MSO Error {code}: {message}'.format(**payload), data=data, info=info, payload=payload)
+ else:
+ self.fail_json(msg='MSO Error:'.format(**payload), data=data, info=info, payload=payload)
+
+ return {}
+
+ def query_objs(self, path, key=None, **kwargs):
+ ''' Query the MSO REST API for objects in a path '''
+ found = []
+ objs = self.request(path, method='GET')
+
+ if objs == {}:
+ return found
+
+ if key is None:
+ key = path
+
+ if key not in objs:
+ self.fail_json(msg="Key '%s' missing from data", data=objs)
+
+ for obj in objs.get(key):
+ for kw_key, kw_value in kwargs.items():
+ if kw_value is None:
+ continue
+ if obj.get(kw_key) != kw_value:
+ break
+ else:
+ found.append(obj)
+ return found
+
+ def query_obj(self, path, **kwargs):
+ ''' Query the MSO REST API for the whole object at a path '''
+ obj = self.request(path, method='GET')
+ if obj == {}:
+ return {}
+ for kw_key, kw_value in kwargs.items():
+ if kw_value is None:
+ continue
+ if obj.get(kw_key) != kw_value:
+ return {}
+ return obj
+
+ def get_obj(self, path, **kwargs):
+ ''' Get a specific object from a set of MSO REST objects '''
+ objs = self.query_objs(path, **kwargs)
+ if len(objs) == 0:
+ return {}
+ if len(objs) > 1:
+ self.fail_json(msg='More than one object matches unique filter: {0}'.format(kwargs))
+ return objs[0]
+
+ def lookup_schema(self, schema):
+ ''' Look up schema and return its id '''
+ if schema is None:
+ return schema
+
+ s = self.get_obj('schemas', displayName=schema)
+ if not s:
+ self.module.fail_json(msg="Schema '%s' is not a valid schema name." % schema)
+ if 'id' not in s:
+ self.module.fail_json(msg="Schema lookup failed for schema '%s': %s" % (schema, s))
+ return s.get('id')
+
+ def lookup_domain(self, domain):
+ ''' Look up a domain and return its id '''
+ if domain is None:
+ return domain
+
+ d = self.get_obj('auth/domains', key='domains', name=domain)
+ if not d:
+ self.module.fail_json(msg="Domain '%s' is not a valid domain name." % domain)
+ if 'id' not in d:
+ self.module.fail_json(msg="Domain lookup failed for domain '%s': %s" % (domain, d))
+ return d.get('id')
+
+ def lookup_roles(self, roles):
+ ''' Look up roles and return their ids '''
+ if roles is None:
+ return roles
+
+ ids = []
+ for role in roles:
+ name = role
+ access_type = "readWrite"
+ if 'name' in role:
+ name = role.get('name')
+ if role.get('access_type') == 'read':
+ access_type = 'readOnly'
+ r = self.get_obj('roles', name=name)
+ if not r:
+ self.module.fail_json(msg="Role '%s' is not a valid role name." % name)
+ if 'id' not in r:
+ self.module.fail_json(msg="Role lookup failed for role '%s': %s" % (name, r))
+ ids.append(dict(roleId=r.get('id'), accessType=access_type))
+ return ids
+
+ def lookup_site(self, site):
+ ''' Look up a site and return its id '''
+ if site is None:
+ return site
+
+ s = self.get_obj('sites', name=site)
+ if not s:
+ self.module.fail_json(msg="Site '%s' is not a valid site name." % site)
+ if 'id' not in s:
+ self.module.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
+ return s.get('id')
+
+ def lookup_sites(self, sites):
+ ''' Look up sites and return their ids '''
+ if sites is None:
+ return sites
+
+ ids = []
+ for site in sites:
+ s = self.get_obj('sites', name=site)
+ if not s:
+ self.module.fail_json(msg="Site '%s' is not a valid site name." % site)
+ if 'id' not in s:
+ self.module.fail_json(msg="Site lookup failed for site '%s': %s" % (site, s))
+ ids.append(dict(siteId=s.get('id'), securityDomains=[]))
+ return ids
+
+ def lookup_tenant(self, tenant):
+ ''' Look up a tenant and return its id '''
+ if tenant is None:
+ return tenant
+
+ t = self.get_obj('tenants', key='tenants', name=tenant)
+ if not t:
+ self.module.fail_json(msg="Tenant '%s' is not valid tenant name." % tenant)
+ if 'id' not in t:
+ self.module.fail_json(msg="Tenant lookup failed for tenant '%s': %s" % (tenant, t))
+ return t.get('id')
+
+ def lookup_remote_location(self, remote_location):
+ ''' Look up a remote location and return its path and id '''
+ if remote_location is None:
+ return None
+
+ remote = self.get_obj('platform/remote-locations', key='remoteLocations', name=remote_location)
+ if 'id' not in remote:
+ self.module.fail_json(msg="No remote location found for remote '%s'" % (remote_location))
+ remote_info = dict(id=remote.get('id'), path=remote.get('credential')['remotePath'])
+ return remote_info
+
+ def lookup_users(self, users):
+ ''' Look up users and return their ids '''
+ # Ensure tenant has at least admin user
+ if users is None:
+ return [dict(userId="0000ffff0000000000000020")]
+
+ ids = []
+ for user in users:
+ u = self.get_obj('users', username=user)
+ if not u:
+ self.module.fail_json(msg="User '%s' is not a valid user name." % user)
+ if 'id' not in u:
+ self.module.fail_json(msg="User lookup failed for user '%s': %s" % (user, u))
+ id = dict(userId=u.get('id'))
+ if id in ids:
+ self.module.fail_json(msg="User '%s' is duplicate." % user)
+ ids.append(id)
+
+ if 'admin' not in users:
+ ids.append(dict(userId="0000ffff0000000000000020"))
+ return ids
+
+ def create_label(self, label, label_type):
+ ''' Create a new label '''
+ return self.request('labels', method='POST', data=dict(displayName=label, type=label_type))
+
+ def lookup_labels(self, labels, label_type):
+ ''' Look up labels and return their ids (create if necessary) '''
+ if labels is None:
+ return None
+
+ ids = []
+ for label in labels:
+ label_obj = self.get_obj('labels', displayName=label)
+ if not label_obj:
+ label_obj = self.create_label(label, label_type)
+ if 'id' not in label_obj:
+ self.module.fail_json(msg="Label lookup failed for label '%s': %s" % (label, label_obj))
+ ids.append(label_obj.get('id'))
+ return ids
+
+ def anp_ref(self, **data):
+ ''' Create anpRef string '''
+ return '/schemas/{schema_id}/templates/{template}/anps/{anp}'.format(**data)
+
+ def epg_ref(self, **data):
+ ''' Create epgRef string '''
+ return '/schemas/{schema_id}/templates/{template}/anps/{anp}/epgs/{epg}'.format(**data)
+
+ def bd_ref(self, **data):
+ ''' Create bdRef string '''
+ return '/schemas/{schema_id}/templates/{template}/bds/{bd}'.format(**data)
+
+ def contract_ref(self, **data):
+ ''' Create contractRef string '''
+ # Support the contract argspec
+ if 'name' in data:
+ data['contract'] = data.get('name')
+ return '/schemas/{schema_id}/templates/{template}/contracts/{contract}'.format(**data)
+
+ def filter_ref(self, **data):
+ ''' Create a filterRef string '''
+ return '/schemas/{schema_id}/templates/{template}/filters/{filter}'.format(**data)
+
+ def vrf_ref(self, **data):
+ ''' Create vrfRef string '''
+ return '/schemas/{schema_id}/templates/{template}/vrfs/{vrf}'.format(**data)
+
+ def ext_epg_ref(self, **data):
+ ''' Create extEpgRef string '''
+ return '/schemas/{schema_id}/templates/{template}/externalEpgs/{external_epg}'.format(**data)
+
+ def vrf_dict_from_ref(self, data):
+ vrf_ref_regex = re.compile(r'\/schemas\/(.*)\/templates\/(.*)\/vrfs\/(.*)')
+ vrf_dict = vrf_ref_regex.search(data)
+ return {
+ 'vrfName': vrf_dict.group(3),
+ 'schemaId': vrf_dict.group(1),
+ 'templateName': vrf_dict.group(2),
+ }
+
+ def dict_from_ref(self, data):
+ if data and data != '':
+ ref_regex = re.compile(r'\/schemas\/(.*)\/templates\/(.*)\/(.*)\/(.*)')
+ dic = ref_regex.search(data)
+ if dic is not None:
+ schema_id = dic.group(1)
+ template_name = dic.group(2)
+ category = dic.group(3)
+ name = dic.group(4)
+ uri_map = {
+ 'vrfs': ['vrfName', 'schemaId', 'templateName'],
+ 'bds': ['bdName', 'schemaId', 'templateName'],
+ 'filters': ['filterName', 'schemaId', 'templateName'],
+ 'contracts': ['contractName', 'schemaId', 'templateName'],
+ 'l3outs': ['l3outName', 'schemaId', 'templateName'],
+ 'anps': ['anpName', 'schemaId', 'templateName'],
+ }
+ result = {
+ uri_map[category][0]: name,
+ uri_map[category][1]: schema_id,
+ uri_map[category][2]: template_name,
+ }
+ return result
+ else:
+ self.module.fail_json(msg="There was no group in search: {data}".format(data=data))
+
+ def make_reference(self, data, reftype, schema_id, template):
+ ''' Create a reference from a dictionary '''
+ # Removes entry from payload
+ if data is None:
+ return None
+
+ if data.get('schema') is not None:
+ schema_obj = self.get_obj('schemas', displayName=data.get('schema'))
+ if not schema_obj:
+ self.fail_json(msg="Referenced schema '{schema}' in {reftype}ref does not exist".format(reftype=reftype, **data))
+ schema_id = schema_obj.get('id')
+
+ if data.get('template') is not None:
+ template = data.get('template')
+
+ refname = '%sName' % reftype
+
+ return {
+ refname: data.get('name'),
+ 'schemaId': schema_id,
+ 'templateName': template,
+ }
+
+ def make_subnets(self, data):
+ ''' Create a subnets list from input '''
+ if data is None:
+ return None
+
+ subnets = []
+ for subnet in data:
+ if 'subnet' in subnet:
+ subnet['ip'] = subnet.get('subnet')
+ if subnet.get('description') is None:
+ subnet['description'] = subnet.get('subnet')
+ subnets.append(dict(
+ ip=subnet.get('ip'),
+ description=str(subnet.get('description')),
+ scope=subnet.get('scope'),
+ shared=subnet.get('shared'),
+ noDefaultGateway=subnet.get('no_default_gateway'),
+ querier=subnet.get('querier'),
+ ))
+
+ return subnets
+
+ def make_dhcp_label(self, data):
+ ''' Create a DHCP policy from input '''
+ if data is None:
+ return None
+ if 'version' in data:
+ data['version'] = int(data.get('version'))
+ if data and 'dhcp_option_policy' in data:
+ dhcp_option_policy = data.get('dhcp_option_policy')
+ if dhcp_option_policy is not None and 'version' in dhcp_option_policy:
+ dhcp_option_policy['version'] = int(dhcp_option_policy.get('version'))
+ data['dhcpOptionLabel'] = dhcp_option_policy
+ del data['dhcp_option_policy']
+ return data
+
+ def sanitize(self, updates, collate=False, required=None, unwanted=None):
+ ''' Clean up unset keys from a request payload '''
+ if required is None:
+ required = []
+ if unwanted is None:
+ unwanted = []
+ self.proposed = deepcopy(self.existing)
+ self.sent = deepcopy(self.existing)
+
+ for key in self.existing:
+ # Remove References
+ if key.endswith('Ref'):
+ del(self.proposed[key])
+ del(self.sent[key])
+ continue
+
+ # Removed unwanted keys
+ elif key in unwanted:
+ del(self.proposed[key])
+ del(self.sent[key])
+ continue
+
+ # Clean up self.sent
+ for key in updates:
+ # Always retain 'id'
+ if key in required:
+ if key in self.existing or updates.get(key) is not None:
+ self.sent[key] = updates.get(key)
+ continue
+
+ # Remove unspecified values
+ elif not collate and updates.get(key) is None:
+ if key in self.existing:
+ del(self.sent[key])
+ continue
+
+ # Remove identical values
+ elif not collate and updates.get(key) == self.existing.get(key):
+ del(self.sent[key])
+ continue
+
+ # Add everything else
+ if updates.get(key) is not None:
+ self.sent[key] = updates.get(key)
+
+ # Update self.proposed
+ self.proposed.update(self.sent)
+
+ def exit_json(self, **kwargs):
+ ''' Custom written method to exit from module. '''
+
+ if self.params.get('state') in ('absent', 'present', 'upload', 'restore', 'download', 'move'):
+ if self.params.get('output_level') in ('debug', 'info'):
+ self.result['previous'] = self.previous
+ # FIXME: Modified header only works for PATCH
+ if not self.has_modified and self.previous != self.existing:
+ self.result['changed'] = True
+ if self.stdout:
+ self.result['stdout'] = self.stdout
+
+ # Return the gory details when we need it
+ if self.params.get('output_level') == 'debug':
+ self.result['method'] = self.method
+ self.result['response'] = self.response
+ self.result['status'] = self.status
+ self.result['url'] = self.url
+
+ if self.params.get('state') in ('absent', 'present'):
+ self.result['sent'] = self.sent
+ self.result['proposed'] = self.proposed
+
+ self.result['current'] = self.existing
+
+ if self.module._diff and self.result.get('changed') is True:
+ self.result['diff'] = dict(
+ before=self.previous,
+ after=self.existing,
+ )
+
+ self.result.update(**kwargs)
+ self.module.exit_json(**self.result)
+
+ def fail_json(self, msg, **kwargs):
+ ''' Custom written method to return info on failure. '''
+
+ if self.params.get('state') in ('absent', 'present'):
+ if self.params.get('output_level') in ('debug', 'info'):
+ self.result['previous'] = self.previous
+ # FIXME: Modified header only works for PATCH
+ if not self.has_modified and self.previous != self.existing:
+ self.result['changed'] = True
+ if self.stdout:
+ self.result['stdout'] = self.stdout
+
+ # Return the gory details when we need it
+ if self.params.get('output_level') == 'debug':
+ if self.url is not None:
+ self.result['method'] = self.method
+ self.result['response'] = self.response
+ self.result['status'] = self.status
+ self.result['url'] = self.url
+
+ if self.params.get('state') in ('absent', 'present'):
+ self.result['sent'] = self.sent
+ self.result['proposed'] = self.proposed
+
+ self.result['current'] = self.existing
+
+ self.result.update(**kwargs)
+ self.module.fail_json(msg=msg, **self.result)
+
+ def check_changed(self):
+ ''' Check if changed by comparing new values from existing'''
+ existing = self.existing
+ if 'password' in existing:
+ existing['password'] = self.sent.get('password')
+ return not issubset(self.sent, existing)
+
+ def update_filter_obj(self, contract_obj, filter_obj, filter_type, contract_display_name=None, updateFilterRef=True):
+ ''' update filter with more information '''
+ if updateFilterRef:
+ filter_obj['filterRef'] = self.dict_from_ref(filter_obj.get('filterRef'))
+ if contract_display_name:
+ filter_obj['displayName'] = contract_display_name
+ else:
+ filter_obj['displayName'] = contract_obj.get('displayName')
+ filter_obj['filterType'] = filter_type
+ filter_obj['contractScope'] = contract_obj.get('scope')
+ filter_obj['contractFilterType'] = contract_obj.get('filterType')
+ return filter_obj
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_backup.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
new file mode 100644
index 00000000..8879b253
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
@@ -0,0 +1,311 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_backup
+short_description: Manages backups
+description:
+- Manage backups on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ location_type:
+ description:
+ - The type of location for the backup to be stored
+ type: str
+ choices: [ local, remote]
+ default: local
+ backup:
+ description:
+ - The name given to the backup
+ type: str
+ aliases: [ name ]
+ remote_location:
+ description:
+ - The remote location's name for the backup to be stored
+ type: str
+ remote_path:
+ description:
+ - This path is relative to the remote location.
+ - A '/' is automatically added between the remote location folder and this path.
+ - This folder structure should already exist on the remote location.
+ type: str
+ description:
+ description:
+ - Brief information about the backup.
+ type: str
+ destination:
+ description:
+ - Location where to download the backup to
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ - Use C(upload) for uploading backup.
+ - Use C(restore) for restoring backup.
+ - Use C(download) for downloading backup.
+ - Use C(move) for moving backup from local to remote location.
+ type: str
+ choices: [ absent, present, query, upload, restore, download, move ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Create a new local backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ description: via Ansible
+ location_type: local
+ state: present
+ delegate_to: localhost
+
+- name: Create a new remote backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ description: via Ansible
+ location_type: remote
+ remote_location: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Move backup to remote location
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup0
+ remote_location: ansible_test
+ remote_path: test
+ state: move
+ delegate_to: localhost
+
+- name: Download a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ destination: ./
+ state: download
+ delegate_to: localhost
+
+- name: Upload a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: ./Backup
+ state: upload
+ delegate_to: localhost
+
+- name: Restore a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: restore
+ delegate_to: localhost
+
+- name: Remove a Backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: absent
+ delegate_to: localhost
+
+- name: Query a backup
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a backup with its complete name
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ backup: Backup_20200721220043
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all backups
+ cisco.mso.mso_backup:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+import os
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ location_type=dict(type='str', default='local', choices=['local', 'remote']),
+ description=dict(type='str'),
+ backup=dict(type='str', aliases=['name']),
+ remote_location=dict(type='str'),
+ remote_path=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query', 'upload', 'restore', 'download', 'move']),
+ destination=dict(type='str')
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['location_type', 'remote', ['remote_location']],
+ ['state', 'absent', ['backup']],
+ ['state', 'present', ['backup']],
+ ['state', 'upload', ['backup']],
+ ['state', 'restore', ['backup']],
+ ['state', 'download', ['backup', 'destination']],
+ ['state', 'move', ['backup', 'remote_location', 'remote_path']]
+ ]
+ )
+
+ description = module.params.get('description')
+ location_type = module.params.get('location_type')
+ state = module.params.get('state')
+ backup = module.params.get('backup')
+ remote_location = module.params.get('remote_location')
+ remote_path = module.params.get('remote_path')
+ destination = module.params.get('destination')
+
+ mso = MSOModule(module)
+
+ backup_names = []
+ mso.existing = mso.query_objs('backups/backupRecords', key='backupRecords')
+ if backup:
+ if mso.existing:
+ data = mso.existing
+ mso.existing = []
+ for backup_info in data:
+ if backup == backup_info.get('name').split('_')[0] or backup == backup_info.get('name'):
+ mso.existing.append(backup_info)
+ backup_names.append(backup_info.get('name'))
+
+ if state == 'query':
+ mso.exit_json()
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if len(mso.existing) > 1:
+ mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(', '.join(backup_names)))
+ elif len(mso.existing) == 1:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request('backups/backupRecords/{id}'.format(id=mso.existing[0].get('id')), method='DELETE')
+ mso.exit_json()
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ name=backup,
+ description=description,
+ locationType=location_type
+ )
+
+ if location_type == 'remote':
+ remote_location_info = mso.lookup_remote_location(remote_location)
+ payload.update(remoteLocationId=remote_location_info.get('id'))
+ if remote_path:
+ remote_path = '{0}/{1}'.format(remote_location_info.get('path'), remote_path)
+ payload.update(remotePath=remote_path)
+
+ mso.proposed = payload
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request('backups', method='POST', data=payload)
+ mso.exit_json()
+
+ elif state == 'upload':
+ mso.previous = mso.existing
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ try:
+ payload = dict(name=(os.path.basename(backup), open(backup, 'rb'), 'application/x-gzip'))
+ mso.existing = mso.request_upload('backups/upload', fields=payload)
+ except Exception:
+ mso.module.fail_json(msg="Backup file '{0}' not found!".format(', '.join(backup.split('/')[-1:])))
+ mso.exit_json()
+
+ if len(mso.existing) == 0:
+ mso.module.fail_json(msg="Backup '{0}' does not exist".format(backup))
+ elif len(mso.existing) > 1:
+ mso.module.fail_json(msg="Multiple backups with same name found. Existing backups with similar names: {0}".format(', '.join(backup_names)))
+
+ elif state == 'restore':
+ mso.previous = mso.existing
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request('backups/{id}/restore'.format(id=mso.existing[0].get('id')), method='PUT')
+
+ elif state == 'download':
+ mso.previous = mso.existing
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request_download('backups/{id}/download'.format(id=mso.existing[0].get('id')), destination=destination)
+
+ elif state == 'move':
+ mso.previous = mso.existing
+ remote_location_info = mso.lookup_remote_location(remote_location)
+ remote_path = '{0}/{1}'.format(remote_location_info.get('path'), remote_path)
+ payload = dict(
+ remoteLocationId=remote_location_info.get('id'),
+ remotePath=remote_path,
+ backupRecordId=mso.existing[0].get('id')
+ )
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request('backups/remote-location', method='POST', data=payload)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
new file mode 100644
index 00000000..843317ec
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
@@ -0,0 +1,167 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module)
+# GNU General Public License v3.0+ (see LICENSE 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: mso_dhcp_option_policy
+short_description: Manage DHCP Option policies.
+description:
+- Manage DHCP Option policies on Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ dhcp_option_policy:
+ description:
+ - Name of the DHCP Option Policy
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - Description of the DHCP Option Policy
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP Option Policy is located.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ description: "My Test DHCP Policy"
+ tenant: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: absent
+ delegate_to: localhost
+
+- name: Query a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+
+- name: Query all DHCP Option Policies
+ cisco.mso.mso_dhcp_option_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_option_policy=dict(type="str", aliases=['name']),
+ description=dict(type="str"),
+ tenant=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['dhcp_option_policy']],
+ ['state', 'present', ['dhcp_option_policy', 'tenant']],
+ ],
+ )
+
+ dhcp_option_policy = module.params.get("dhcp_option_policy")
+ description = module.params.get("description")
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/option"
+
+ # Query for existing object(s)
+ if dhcp_option_policy:
+ mso.existing = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies")
+ if mso.existing:
+ policy_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = '{0}/{1}'.format(path, policy_id)
+ else:
+ mso.existing = mso.query_objs(path, key="DhcpRelayPolicies")
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE", data=mso.sent)
+
+ elif state == "present":
+ tenant_id = mso.lookup_tenant(tenant)
+ payload = dict(
+ name=dhcp_option_policy,
+ desc=description,
+ policyType="dhcp",
+ policySubtype="option",
+ tenantId=tenant_id,
+ )
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
new file mode 100644
index 00000000..91cf3ad8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# Copyright: (c) 2020, Jorge Gomez (@jgomezve) <jgomezve@cisco.com> (based on mso_dhcp_relay_policy module)
+# GNU General Public License v3.0+ (see LICENSE 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: mso_dhcp_option_policy_option
+short_description: Manage DHCP options in a DHCP Option policy.
+description:
+- Manage DHCP options in a DHCP Option policy on Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ dhcp_option_policy:
+ description:
+ - Name of the DHCP Option Policy
+ type: str
+ required: yes
+ aliases: [ name ]
+ name:
+ description:
+ - Name of the option in the DHCP Option Policy
+ type: str
+ aliases: [ option ]
+ id:
+ description:
+ - Id of the option in the DHCP Option Policy
+ type: int
+ data:
+ description:
+ - Data of the DHCP option in the DHCP Option Policy
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ id: 1
+ data: Data stored in the option
+ state: present
+ delegate_to: localhost
+
+- name: Remove a option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a option to a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ name: ansible_test
+ state: query
+ delegate_to: localhost
+
+- name: Query all option of a DHCP Option Policy
+ cisco.mso.mso_dhcp_option_policy_option:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_option_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_option_policy=dict(type="str", required=True),
+ name=dict(type="str", aliases=['option']),
+ id=dict(type="int"),
+ data=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["name", "id", "data"]],
+ ["state", "absent", ["name"]],
+ ],
+ )
+
+ dhcp_option_policy = module.params.get("dhcp_option_policy")
+ option_id = module.params.get("id")
+ name = module.params.get("name")
+ data = module.params.get("data")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/option"
+
+ option_index = None
+ previous_option = {}
+
+ # Query for existing object(s)
+ dhcp_option_obj = mso.get_obj(path, name=dhcp_option_policy, key="DhcpRelayPolicies")
+ if 'id' not in dhcp_option_obj:
+ mso.fail_json(msg="DHCP Option Policy '{0}' is not a valid DHCP Option Policy name.".format(dhcp_option_policy))
+ policy_id = dhcp_option_obj.get("id")
+ options = []
+ if "dhcpOption" in dhcp_option_obj:
+ options = dhcp_option_obj.get('dhcpOption')
+ for index, opt in enumerate(options):
+ if opt.get('name') == name:
+ previous_option = opt
+ option_index = index
+
+ # If we found an existing object, continue with it
+ path = '{0}/{1}'.format(path, policy_id)
+
+ if state == "query":
+ mso.existing = options
+ if name is not None:
+ mso.existing = previous_option
+ mso.exit_json()
+
+ mso.previous = previous_option
+ if state == "absent":
+ option = {}
+ if previous_option and option_index is not None:
+ options.pop(option_index)
+
+ elif state == "present":
+ option = dict(
+ id=str(option_id),
+ name=name,
+ data=data,
+ )
+ if option_index is not None:
+ options[option_index] = option
+ else:
+ options.append(option)
+
+ if module.check_mode:
+ mso.existing = option
+ else:
+ mso.existing = dhcp_option_obj
+ dhcp_option_obj["dhcpOption"] = options
+ mso.sanitize(dhcp_option_obj, collate=True)
+ new_dhcp_option_obj = mso.request(path, method="PUT", data=mso.sent)
+ mso.existing = {}
+ for index, opt in enumerate(new_dhcp_option_obj.get('dhcpOption')):
+ if opt.get('name') == name:
+ mso.existing = opt
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
new file mode 100644
index 00000000..d62aa304
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
@@ -0,0 +1,166 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_dhcp_relay_policy
+short_description: Manage DHCP Relay policies.
+description:
+- Manage DHCP Relay policies on Cisco Multi-Site Orchestrator.
+author:
+- Jorge Gomez (@jorgegome2307)
+options:
+ dhcp_relay_policy:
+ description:
+ - Name of the DHCP Relay Policy
+ type: str
+ aliases: [ name ]
+ description:
+ description:
+ - Description of the DHCP Relay Policy
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP Relay Policy is located.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ description: "My Test DHCP Policy"
+ tenant: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: absent
+ delegate_to: localhost
+
+- name: Query a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+
+- name: Query all DHCP Relay Policies
+ cisco.mso.mso_dhcp_relay_policy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_relay_policy=dict(type="str", aliases=['name']),
+ description=dict(type="str"),
+ tenant=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['dhcp_relay_policy']],
+ ['state', 'present', ['dhcp_relay_policy', 'tenant']],
+ ],
+ )
+
+ dhcp_relay_policy = module.params.get("dhcp_relay_policy")
+ description = module.params.get("description")
+ tenant = module.params.get("tenant")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/relay"
+
+ # Query for existing object(s)
+ if dhcp_relay_policy:
+ mso.existing = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies")
+ if mso.existing:
+ policy_id = mso.existing.get("id")
+ # If we found an existing object, continue with it
+ path = '{0}/{1}'.format(path, policy_id)
+ else:
+ mso.existing = mso.query_objs(path, key="DhcpRelayPolicies")
+
+ mso.previous = mso.existing
+
+ if state == "absent":
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method="DELETE", data=mso.sent)
+
+ elif state == "present":
+ tenant_id = mso.lookup_tenant(tenant)
+ payload = dict(
+ name=dhcp_relay_policy,
+ desc=description,
+ policyType="dhcp",
+ policySubtype="relay",
+ tenantId=tenant_id,
+ )
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="PUT", data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method="POST", data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
new file mode 100644
index 00000000..8454a50b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Jorge Gomez Velasquez <jgomezve@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_dhcp_relay_policy_provider
+short_description: Manage DHCP providers in a DHCP Relay policy.
+description:
+- Manage DHCP providers in a DHCP Relay policy on Cisco Multi-Site Orchestrator.
+author:
+- Jorge Gomez (@jorgegome2307)
+options:
+ dhcp_relay_policy:
+ description:
+ - Name of the DHCP Relay Policy
+ type: str
+ required: yes
+ aliases: [ name ]
+ ip:
+ description:
+ - IP address of the DHCP Server
+ type: str
+ tenant:
+ description:
+ - Tenant where the DHCP provider is located.
+ type: str
+ schema:
+ description:
+ - Schema where the DHCP provider is configured
+ type: str
+ template:
+ description:
+ - template where the DHCP provider is configured
+ type: str
+ application_profile:
+ description:
+ - Application Profile where the DHCP provider is configured
+ type: str
+ aliases: [ anp ]
+ endpoint_group:
+ description:
+ - EPG where the DHCP provider is configured
+ type: str
+ aliases: [ epg ]
+ external_endpoint_group:
+ description:
+ - External EPG where the DHCP provider is configured
+ type: str
+ aliases: [ ext_epg, external_epg ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+EXAMPLES = r"""
+- name: Add a new provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a provider to a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ tenant: ansible_test
+ schema: ansible_test
+ template: Template 1
+ application_profile: ansible_test
+ endpoint_group: ansible_test
+ state: query
+ delegate_to: localhost
+
+- name: Query all provider of a DHCP Relay Policy
+ cisco.mso.mso_dhcp_relay_policy_provider:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ dhcp_relay_policy: my_test_dhcp_policy
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ dhcp_relay_policy=dict(type="str", required=True, aliases=['name']),
+ ip=dict(type="str"),
+ tenant=dict(type="str"),
+ schema=dict(type="str"),
+ template=dict(type="str"),
+ application_profile=dict(type="str", aliases=['anp']),
+ endpoint_group=dict(type="str", aliases=['epg']),
+ external_endpoint_group=dict(type="str", aliases=['ext_epg', 'external_epg']),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["ip", "tenant", "schema", "template"]],
+ ["state", "absent", ["tenant", "schema", "template"]],
+ ],
+ )
+
+ dhcp_relay_policy = module.params.get("dhcp_relay_policy")
+ ip = module.params.get("ip")
+ tenant = module.params.get("tenant")
+ schema = module.params.get("schema")
+ template = module.params.get("template")
+ if template is not None:
+ template = template.replace(' ', '')
+ application_profile = module.params.get("application_profile")
+ endpoint_group = module.params.get("endpoint_group")
+ external_endpoint_group = module.params.get("external_endpoint_group")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ path = "policies/dhcp/relay"
+
+ tenant_id = mso.lookup_tenant(tenant)
+ schema_id = mso.lookup_schema(schema)
+
+ provider = dict(
+ addr=ip,
+ externalEpgRef='',
+ epgRef='',
+ l3Ref='',
+ tenantId=tenant_id,
+ )
+ provider_index = None
+ previous_provider = {}
+
+ if application_profile is not None and endpoint_group is not None:
+ provider['epgRef'] = '/schemas/{schemaId}/templates/{templateName}/anps/{app}/epgs/{epg}'.format(
+ schemaId=schema_id, templateName=template, app=application_profile, epg=endpoint_group,
+ )
+ elif external_endpoint_group is not None:
+ provider['externalEpgRef'] = '/schemas/{schemaId}/templates/{templateName}/externalEpgs/{ext_epg}'.format(
+ schemaId=schema_id, templateName=template, ext_epg=external_endpoint_group
+ )
+
+ # Query for existing object(s)
+ dhcp_relay_obj = mso.get_obj(path, name=dhcp_relay_policy, key="DhcpRelayPolicies")
+ if 'id' not in dhcp_relay_obj:
+ mso.fail_json(msg="DHCP Relay Policy '{0}' is not a valid DHCP Relay Policy name.".format(dhcp_relay_policy))
+ policy_id = dhcp_relay_obj.get("id")
+ providers = []
+ if "provider" in dhcp_relay_obj:
+ providers = dhcp_relay_obj.get('provider')
+ for index, prov in enumerate(providers):
+ if (
+ (provider.get('epgRef') != '' and prov.get('epgRef') == provider.get('epgRef'))
+ or (provider.get('externalEpgRef') != '' and prov.get('externalEpgRef') == provider.get('externalEpgRef'))
+ ):
+ previous_provider = prov
+ provider_index = index
+
+ # If we found an existing object, continue with it
+ path = '{0}/{1}'.format(path, policy_id)
+
+ if state == "query":
+ mso.existing = providers
+ if endpoint_group is not None or external_endpoint_group is not None:
+ mso.existing = previous_provider
+ mso.exit_json()
+
+ if endpoint_group is None and external_endpoint_group is None:
+ mso.fail_json(msg="Missing either endpoint_group or external_endpoint_group required attribute.")
+
+ mso.previous = previous_provider
+ if state == "absent":
+ provider = {}
+ if previous_provider:
+ if provider_index is not None:
+ providers.pop(provider_index)
+
+ elif state == "present":
+ if provider_index is not None:
+ providers[provider_index] = provider
+ else:
+ providers.append(provider)
+
+ if module.check_mode:
+ mso.existing = provider
+ else:
+ mso.existing = dhcp_relay_obj
+ dhcp_relay_obj["provider"] = providers
+ mso.sanitize(dhcp_relay_obj, collate=True)
+ new_dhcp_relay_obj = mso.request(path, method="PUT", data=mso.sent)
+ mso.existing = {}
+ for index, prov in enumerate(new_dhcp_relay_obj.get('provider')):
+ if (
+ (provider.get('epgRef') != '' and prov.get('epgRef') == provider.get('epgRef'))
+ or (provider.get('externalEpgRef') != '' and prov.get('externalEpgRef') == provider.get('externalEpgRef'))
+ ):
+ mso.existing = prov
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_label.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_label.py
new file mode 100644
index 00000000..f2e1fd6d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_label.py
@@ -0,0 +1,165 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_label
+short_description: Manage labels
+description:
+- Manage labels on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ label:
+ description:
+ - The name of the label.
+ type: str
+ aliases: [ name ]
+ type:
+ description:
+ - The type of the label.
+ type: str
+ choices: [ site ]
+ default: site
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ type: site
+ state: present
+ delegate_to: localhost
+
+- name: Remove a label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ state: absent
+ delegate_to: localhost
+
+- name: Query a label
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ label: Belgium
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all labels
+ cisco.mso.mso_label:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ label=dict(type='str', aliases=['name']),
+ type=dict(type='str', default='site', choices=['site']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['label']],
+ ['state', 'present', ['label']],
+ ],
+ )
+
+ label = module.params.get('label')
+ label_type = module.params.get('type')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ label_id = None
+ path = 'labels'
+
+ # Query for existing object(s)
+ if label:
+ mso.existing = mso.get_obj(path, displayName=label)
+ if mso.existing:
+ label_id = mso.existing.get('id')
+ # If we found an existing object, continue with it
+ path = 'labels/{id}'.format(id=label_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE')
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=label_id,
+ displayName=label,
+ type=label_type,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='PUT', data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='POST', data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_rest.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
new file mode 100644
index 00000000..51c183c3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_rest
+short_description: Direct access to the Cisco MSO REST API
+description:
+- Enables the management of the Cisco MSO fabric through direct access to the Cisco MSO REST API.
+- This module is not idempotent and does not report changes.
+options:
+ method:
+ description:
+ - The HTTP method of the request.
+ - Using C(delete) is typically used for deleting objects.
+ - Using C(get) is typically used for querying objects.
+ - Using C(post) is typically used for modifying objects.
+ - Using C(put) is typically used for modifying existing objects.
+ - Using C(patch) is typically also used for modifying existing objects.
+ type: str
+ choices: [ delete, get, post, put, patch ]
+ default: get
+ aliases: [ action ]
+ path:
+ description:
+ - URI being used to execute API calls.
+ type: str
+ required: yes
+ aliases: [ uri ]
+ content:
+ description:
+ - Sets the payload of the API request directly.
+ - This may be convenient to template simple requests.
+ - For anything complex use the C(template) lookup plugin (see examples).
+ type: raw
+ aliases: [ payload ]
+extends_documentation_fragment:
+- cisco.mso.modules
+
+notes:
+- Most payloads are known not to be idempotent, so be careful when constructing payloads.
+seealso:
+- module: cisco.mso.mso_tenant
+author:
+- Anvitha Jain (@anvitha-jain)
+'''
+
+EXAMPLES = r'''
+- name: Add schema (JSON)
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: /mso/api/v1/schemas
+ method: post
+ content:
+ {
+ "displayName": "{{ mso_schema | default('ansible_test') }}",
+ "templates": [{
+ "name": "Template_1",
+ "tenantId": "{{ add_tenant.jsondata.id }}",
+ "displayName": "Template_1",
+ "templateSubType": [],
+ "templateType": "stretched-template",
+ "anps": [],
+ "contracts": [],
+ "vrfs": [],
+ "bds": [],
+ "filters": [],
+ "externalEpgs": [],
+ "serviceGraphs": [],
+ "intersiteL3outs": []
+ }],
+ "sites": [],
+ "_updateVersion": 0
+ }
+ delegate_to: localhost
+
+- name: Query schema
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: /mso/api/v1/schemas
+ method: get
+ delegate_to: localhost
+
+- name: Patch schema (YAML)
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ path: "/mso/api/v1/schemas/{{ add_schema.jsondata.id }}"
+ method: patch
+ content:
+ - op: add
+ path: /templates/Template_1/anps/-
+ value:
+ name: AP2
+ displayName: AP2
+ epgs: []
+ _updateVersion: 0
+ delegate_to: localhost
+
+- name: Add a tenant from a templated payload file from templates
+ cisco.mso.mso_rest:
+ host: mso
+ username: admin
+ password: SomeSecretPassword
+ method: post
+ path: /api/v1/tenants
+ content: "{{ lookup('template', 'mso/tenant.json.j2') }}"
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+'''
+
+import json
+
+# Optional, only used for YAML validation
+try:
+ import yaml
+ HAS_YAML = True
+except Exception:
+ HAS_YAML = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible.module_utils.urls import fetch_url
+from ansible.module_utils._text import to_text
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ path=dict(type='str', required=True, aliases=['uri']),
+ method=dict(type='str', default='get', choices=['delete', 'get', 'post', 'put', 'patch'], aliases=['action']),
+ content=dict(type='raw', aliases=['payload']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ )
+
+ content = module.params.get('content')
+ path = module.params.get('path')
+
+ mso = MSOModule(module)
+ mso.result['status'] = -1 # Ensure we always return a status
+
+ # Validate content/payload
+ if content and (isinstance(content, dict) or isinstance(content, list)):
+ # Validate inline YAML/JSON
+ content = json.dumps(content)
+ elif content and isinstance(content, str) and HAS_YAML:
+ try:
+ # Validate YAML/JSON string
+ content = json.dumps(yaml.safe_load(content))
+ except Exception as e:
+ module.fail_json(msg='Failed to parse provided JSON/YAML payload: %s' % to_text(e), exception=to_text(e), payload=content)
+
+ # Perform actual request using auth cookie (Same as mso.request())
+ if 'port' in mso.params and mso.params.get('port') is not None:
+ mso.url = '%(protocol)s://%(host)s:%(port)s/' % mso.params + path.lstrip('/')
+ else:
+ mso.url = '%(protocol)s://%(host)s/' % mso.params + path.lstrip('/')
+
+ mso.method = mso.params.get('method').upper()
+
+ # Perform request
+ resp, info = fetch_url(module, mso.url,
+ data=content,
+ headers=mso.headers,
+ method=mso.method,
+ timeout=mso.params.get('timeout'),
+ use_proxy=mso.params.get('use_proxy'))
+
+ mso.response = info.get('msg')
+ mso.status = info.get('status', -1)
+ mso.result['status'] = info.get('status', -1)
+
+ # Report failure
+ if info.get('status') not in [200, 201, 202, 204]:
+ try:
+ # MSO error
+ mso.response_json(info['body'])
+ mso.fail_json(msg='MSO Error %(code)s: %(message)s - %(info)s' % mso.error)
+ except KeyError:
+ # Connection error
+ mso.fail_json(msg='Connection failed for %(url)s. %(msg)s' % info)
+
+ mso.response_json(resp.read())
+
+ if mso.method != 'GET':
+ mso.result['changed'] = True
+
+ mso.result['jsondata'] = mso.jsondata
+
+ # Report success
+ mso.exit_json(**mso.result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_role.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_role.py
new file mode 100644
index 00000000..69ad4fe7
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_role.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_role
+short_description: Manage roles
+description:
+- Manage roles on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ role:
+ description:
+ - The name of the role.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name of the role to be displayed in the web UI.
+ type: str
+ description:
+ description:
+ - The description of the role.
+ type: str
+ read_permissions:
+ description:
+ - A list of read permissions tied to this role.
+ type: list
+ elements: str
+ choices:
+ - backup-db
+ - manage-audit-records
+ - manage-labels
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ - platform-logs
+ - view-all-audit-records
+ - view-labels
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ write_permissions:
+ description:
+ - A list of write permissions tied to this role.
+ type: list
+ elements: str
+ aliases: [ permissions ]
+ choices:
+ - backup-db
+ - manage-audit-records
+ - manage-labels
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ - platform-logs
+ - view-all-audit-records
+ - view-labels
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ display_name: Read Only
+ description: Read-only access for troubleshooting
+ read_permissions:
+ - view-roles
+ - view-schemas
+ - view-sites
+ - view-tenants
+ - view-tenant-schemas
+ - view-users
+ write_permissions:
+ - manage-roles
+ - manage-schemas
+ - manage-sites
+ - manage-tenants
+ - manage-tenant-schemas
+ - manage-users
+ state: present
+ delegate_to: localhost
+
+- name: Remove a role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ state: absent
+ delegate_to: localhost
+
+- name: Query a role
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ role: readOnly
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all roles
+ cisco.mso.mso_role:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ role=dict(type='str', aliases=['name']),
+ display_name=dict(type='str'),
+ description=dict(type='str'),
+ read_permissions=dict(type='list', elements='str', choices=[
+ 'backup-db',
+ 'manage-audit-records',
+ 'manage-labels',
+ 'manage-roles',
+ 'manage-schemas',
+ 'manage-sites',
+ 'manage-tenants',
+ 'manage-tenant-schemas',
+ 'manage-users',
+ 'platform-logs',
+ 'view-all-audit-records',
+ 'view-labels',
+ 'view-roles',
+ 'view-schemas',
+ 'view-sites',
+ 'view-tenants',
+ 'view-tenant-schemas',
+ 'view-users',
+ ]),
+ write_permissions=dict(type='list', elements='str', aliases=['permissions'], choices=[
+ 'backup-db',
+ 'manage-audit-records',
+ 'manage-labels',
+ 'manage-roles',
+ 'manage-schemas',
+ 'manage-sites',
+ 'manage-tenants',
+ 'manage-tenant-schemas',
+ 'manage-users',
+ 'platform-logs',
+ 'view-all-audit-records',
+ 'view-labels',
+ 'view-roles',
+ 'view-schemas',
+ 'view-sites',
+ 'view-tenants',
+ 'view-tenant-schemas',
+ 'view-users',
+ ]),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['role']],
+ ['state', 'present', ['role']],
+ ],
+ )
+
+ role = module.params.get('role')
+ description = module.params.get('description')
+ read_permissions = module.params.get('read_permissions')
+ write_permissions = module.params.get('write_permissions')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ role_id = None
+ path = 'roles'
+
+ # Query for existing object(s)
+ if role:
+ mso.existing = mso.get_obj(path, name=role)
+ if mso.existing:
+ role_id = mso.existing.get('id')
+ # If we found an existing object, continue with it
+ path = 'roles/{id}'.format(id=role_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE')
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=role_id,
+ name=role,
+ displayName=role,
+ description=description,
+ readPermissions=read_permissions,
+ writePermissions=write_permissions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='PUT', data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='POST', data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
new file mode 100644
index 00000000..5cf03aab
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema
+short_description: Manage schemas
+description:
+- Manage schemas on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, query ]
+ default: query
+notes:
+- Due to restrictions of the MSO REST API this module cannot create empty schemas (i.e. schemas without templates).
+ Use the M(cisco.mso.mso_schema_template) to automatically create schemas with templates.
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Remove schemas
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a schema
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all schemas
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', aliases=['name']),
+ # messages=dict(type='dict'),
+ # associations=dict(type='list'),
+ # health_faults=dict(type='list'),
+ # references=dict(type='dict'),
+ # policy_states=dict(type='list'),
+ state=dict(type='str', default='query', choices=['absent', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['schema']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ schema_id = None
+ path = 'schemas'
+
+ # Query for existing object(s)
+ if schema:
+ mso.existing = mso.get_obj(path, displayName=schema)
+ if mso.existing:
+ schema_id = mso.existing.get('id')
+ path = 'schemas/{id}'.format(id=schema_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE')
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
new file mode 100644
index 00000000..5879c168
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
@@ -0,0 +1,200 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site
+short_description: Manage sites in schemas
+description:
+- Manage sites on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site to manage.
+ type: str
+ template:
+ description:
+ - The name of the template.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template
+- module: cisco.mso.mso_site
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site to a schema
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site from a schema
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a schema site
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all schema sites
+ cisco.mso.mso_schema_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', aliases=['name']),
+ template=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['site', 'template']],
+ ['state', 'present', ['site', 'template']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template')
+ if template is not None:
+ template = template.replace(' ', '')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ # Schema exists
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ mso.existing = {}
+ if 'sites' in schema_obj:
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if template:
+ if (site_id, template) in sites:
+ site_idx = sites.index((site_id, template))
+ site_path = '/sites/{0}'.format(site_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]
+ else:
+ mso.existing = schema_obj.get('sites')
+
+ if state == 'query':
+ if not mso.existing:
+ if template:
+ mso.fail_json(msg="Template '{0}' not found".format(template))
+ else:
+ mso.existing = []
+ mso.exit_json()
+
+ sites_path = '/sites'
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ # Remove existing site
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=site_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ # Add new site
+ payload = dict(
+ siteId=site_id,
+ templateName=template,
+ anps=[],
+ bds=[],
+ contracts=[],
+ externalEpgs=[],
+ intersiteL3outs=[],
+ serviceGraphs=[],
+ vrfs=[],
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op='add', path=sites_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
new file mode 100644
index 00000000..2752e3d2
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp
+short_description: Manage site-local Application Network Profiles (ANPs) in schema template
+description:
+- Manage site-local ANPs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site ANP
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site ANPs
+ cisco.mso.mso_schema_site_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['anp']],
+ ['state', 'present', ['anp']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
+
+ if anp is not None and anp_ref in anps:
+ anp_idx = anps.index(anp_ref)
+ anp_path = '/sites/{0}/anps/{1}'.format(site_template, anp)
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]
+
+ if state == 'query':
+ if anp is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps']
+ elif not mso.existing:
+ mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
+ mso.exit_json()
+
+ anps_path = '/sites/{0}/anps'.format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=anp_path))
+
+ elif state == 'present':
+
+ payload = dict(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if not mso.existing:
+ ops.append(dict(op='add', path=anps_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
new file mode 100644
index 00000000..b7fe7fbc
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg
+short_description: Manage site-local Endpoint Groups (EPGs) in schema template
+description:
+- Manage site-local EPGs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site_anp
+- module: cisco.mso.mso_schema_site_anp_epg_subnet
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site EPG
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site EPG
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPGs
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPGs
+ cisco.mso.mso_schema_site_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['epg']],
+ ['state', 'present', ['epg']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
+ if epg is not None and epg_ref in epgs:
+ epg_idx = epgs.index(epg_ref)
+ epg_path = '/sites/{0}/anps/{1}/epgs/{2}'.format(site_template, anp, epg)
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]
+
+ if state == 'query':
+ if epg is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']
+ elif not mso.existing:
+ mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
+ mso.exit_json()
+
+ epgs_path = '/sites/{0}/anps/{1}/epgs'.format(site_template, anp)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=epg_path))
+
+ elif state == 'present':
+
+ payload = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if not mso.existing:
+ ops.append(dict(op='add', path=epgs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
new file mode 100644
index 00000000..52d913ae
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
@@ -0,0 +1,474 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Nirav Katarmal (@nkatarmal-crest) <nirav.katarmal@crestdatasys.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg_domain
+short_description: Manage site-local EPG domains in schema template
+description:
+- Manage site-local EPG domains in schema template on Cisco ACI Multi-Site.
+author:
+- Nirav Katarmal (@nkatarmal-crest)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: yes
+ domain_association_type:
+ description:
+ - The type of domain to associate.
+ type: str
+ choices: [ vmmDomain, l3ExtDomain, l2ExtDomain, physicalDomain, fibreChannelDomain ]
+ domain_profile:
+ description:
+ - The domain profile name.
+ type: str
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the domain.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ resolution_immediacy:
+ description:
+ - Determines when the policies should be resolved and available.
+ - Defaults to C(lazy) when unset during creation.
+ type: str
+ choices: [ immediate, lazy, pre-provision ]
+ micro_seg_vlan_type:
+ description:
+ - Virtual LAN type for microsegmentation. This attribute can only be used with vmmDomain domain association.
+ - vlan is currently the only accepted value.
+ type: str
+ micro_seg_vlan:
+ description:
+ - Virtual LAN for microsegmentation. This attribute can only be used with vmmDomain domain association.
+ type: int
+ port_encap_vlan_type:
+ description:
+ - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
+ - vlan is currently the only accepted value.
+ type: str
+ port_encap_vlan:
+ description:
+ - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
+ type: int
+ vlan_encap_mode:
+ description:
+ - Which VLAN enacap mode to use. This attribute can only be used with vmmDomain domain association.
+ type: str
+ choices: [ static, dynamic ]
+ allow_micro_segmentation:
+ description:
+ - Specifies microsegmentation is enabled or not. This attribute can only be used with vmmDomain domain association.
+ type: bool
+ switch_type:
+ description:
+ - Which switch type to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ type: str
+ switching_mode:
+ description:
+ - Which switching mode to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ type: str
+ enhanced_lagpolicy_name:
+ description:
+ - EPG enhanced lagpolicy name. This attribute can only be used with vmmDomain domain association.
+ type: str
+ enhanced_lagpolicy_dn:
+ description:
+ - Distinguished name of EPG lagpolicy. This attribute can only be used with vmmDomain domain association.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new domain to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: present
+ delegate_to: localhost
+
+- name: Remove a domain from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: absent
+ delegate_to: localhost
+
+- name: Query a domain associated with a specific site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all domains associated with a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ domain_association_type=dict(type='str', choices=['vmmDomain', 'l3ExtDomain', 'l2ExtDomain', 'physicalDomain', 'fibreChannelDomain']),
+ domain_profile=dict(type='str'),
+ deployment_immediacy=dict(type='str', choices=['immediate', 'lazy']),
+ resolution_immediacy=dict(type='str', choices=['immediate', 'lazy', 'pre-provision']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ micro_seg_vlan_type=dict(type='str'),
+ micro_seg_vlan=dict(type='int'),
+ port_encap_vlan_type=dict(type='str'),
+ port_encap_vlan=dict(type='int'),
+ vlan_encap_mode=dict(type='str', choices=['static', 'dynamic']),
+ allow_micro_segmentation=dict(type='bool'),
+ switch_type=dict(type='str'),
+ switching_mode=dict(type='str'),
+ enhanced_lagpolicy_name=dict(type='str'),
+ enhanced_lagpolicy_dn=dict(type='str'),
+
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['domain_association_type', 'domain_profile', 'deployment_immediacy', 'resolution_immediacy']],
+ ['state', 'present', ['domain_association_type', 'domain_profile', 'deployment_immediacy', 'resolution_immediacy']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ domain_association_type = module.params.get('domain_association_type')
+ domain_profile = module.params.get('domain_profile')
+ deployment_immediacy = module.params.get('deployment_immediacy')
+ resolution_immediacy = module.params.get('resolution_immediacy')
+ state = module.params.get('state')
+ micro_seg_vlan_type = module.params.get('micro_seg_vlan_type')
+ micro_seg_vlan = module.params.get('micro_seg_vlan')
+ port_encap_vlan_type = module.params.get('port_encap_vlan_type')
+ port_encap_vlan = module.params.get('port_encap_vlan')
+ vlan_encap_mode = module.params.get('vlan_encap_mode')
+ allow_micro_segmentation = module.params.get('allow_micro_segmentation')
+ switch_type = module.params.get('switch_type')
+ switching_mode = module.params.get('switching_mode')
+ enhanced_lagpolicy_name = module.params.get('enhanced_lagpolicy_name')
+ enhanced_lagpolicy_dn = module.params.get('enhanced_lagpolicy_dn')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ sites_list = [s.get('siteId') + '/' + s.get('templateName') for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ', '.join(sites_list)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ''
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj['sites'][site_idx]['anps']]
+ anps_in_temp = [a.get('name') for a in schema_obj['templates'][template_idx]['anps']]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ else:
+ # Update anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = '/sites/{0}/anps/-'.format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Update anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if 'anpRef' not in payload:
+ epgs = [e.get('epgRef') for e in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs']]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level?
+ if ('anpRef' not in payload and epg_ref not in epgs) or 'anpRef' in payload:
+ epgs_in_temp = [e.get('name') for e in schema_obj['templates'][template_idx]['anps'][template_anp_idx]['epgs']]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ', '.join(epgs_in_temp), epg_ref))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if 'anpRef' not in payload:
+ op_path = '/sites/{0}/anps/{1}/epgs/-'.format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload['epgs'] = [new_epg]
+
+ # Update index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ if domain_association_type == 'vmmDomain':
+ domain_dn = 'uni/vmmp-VMware/dom-{0}'.format(domain_profile)
+ elif domain_association_type == 'l3ExtDomain':
+ domain_dn = 'uni/l3dom-{0}'.format(domain_profile)
+ elif domain_association_type == 'l2ExtDomain':
+ domain_dn = 'uni/l2dom-{0}'.format(domain_profile)
+ elif domain_association_type == 'physicalDomain':
+ domain_dn = 'uni/phys-{0}'.format(domain_profile)
+ elif domain_association_type == 'fibreChannelDomain':
+ domain_dn = 'uni/fc-{0}'.format(domain_profile)
+ else:
+ domain_dn = ''
+
+ # Get Domains
+ # If anp at site level and epg is at site level
+ if 'anpRef' not in payload and 'epgRef' not in payload:
+ domains = [dom.get('dn') for dom in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations']]
+ if domain_dn in domains:
+ domain_idx = domains.index(domain_dn)
+ domain_path = '/sites/{0}/anps/{1}/epgs/{2}/domainAssociations/{3}'.format(site_template, anp, epg, domain_idx)
+ mso.existing = schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations'][domain_idx]
+
+ if state == 'query':
+ if domain_association_type is None or domain_profile is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['domainAssociations']
+ elif not mso.existing:
+ mso.fail_json(msg="Domain association '{domain_association_type}/{domain_profile}' not found".format(
+ domain_association_type=domain_association_type,
+ domain_profile=domain_profile))
+ mso.exit_json()
+
+ domains_path = '/sites/{0}/anps/{1}/epgs/{2}/domainAssociations'.format(site_template, anp, epg)
+ ops = []
+ new_domain = dict(
+ dn=domain_dn,
+ domainType=domain_association_type,
+ deploymentImmediacy=deployment_immediacy,
+ resolutionImmediacy=resolution_immediacy,
+ )
+
+ if domain_association_type == 'vmmDomain':
+ vmmDomainProperties = {}
+ if micro_seg_vlan_type and micro_seg_vlan:
+ microSegVlan = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan)
+ vmmDomainProperties['microSegVlan'] = microSegVlan
+ elif not micro_seg_vlan_type and micro_seg_vlan:
+ mso.fail_json(msg="micro_seg_vlan_type is required when micro_seg_vlan is provided.")
+ elif micro_seg_vlan_type and not micro_seg_vlan:
+ mso.fail_json(msg="micro_seg_vlan is required when micro_seg_vlan_type is provided.")
+
+ if port_encap_vlan_type and port_encap_vlan:
+ portEncapVlan = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan)
+ vmmDomainProperties['portEncapVlan'] = portEncapVlan
+ elif not port_encap_vlan_type and port_encap_vlan:
+ mso.fail_json(msg="port_encap_vlan_type is required when port_encap_vlan is provided.")
+ elif port_encap_vlan_type and not port_encap_vlan:
+ mso.fail_json(msg="port_encap_vlan is required when port_encap_vlan_type is provided.")
+
+ if vlan_encap_mode:
+ vmmDomainProperties['vlanEncapMode'] = vlan_encap_mode
+
+ if allow_micro_segmentation:
+ vmmDomainProperties['allowMicroSegmentation'] = allow_micro_segmentation
+ if switch_type:
+ vmmDomainProperties['switchType'] = switch_type
+ if switching_mode:
+ vmmDomainProperties['switchingMode'] = switching_mode
+
+ if enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
+ enhancedLagPol = dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn)
+ epgLagPol = dict(enhancedLagPol=enhancedLagPol)
+ vmmDomainProperties['epgLagPol'] = epgLagPol
+ elif not enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
+ mso.fail_json(msg="enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided.")
+ elif enhanced_lagpolicy_name and not enhanced_lagpolicy_dn:
+ mso.fail_json(msg="enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided.")
+
+ if vmmDomainProperties:
+ new_domain['vmmDomainProperties'] = vmmDomainProperties
+
+ # If payload is empty, anp and EPG already exist at site level
+ if not payload:
+ op_path = domains_path + '/-'
+ payload = new_domain
+
+ # If payload exists
+ else:
+ # If anp already exists at site level...(AND payload != epg as well?)
+ if 'anpRef' not in payload:
+ payload['domainAssociations'] = [new_domain]
+ else:
+ payload['epgs'][0]['domainAssociations'] = [new_domain]
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=domain_path))
+ elif state == 'present':
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=domain_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=op_path, value=mso.sent))
+
+ mso.existing = new_domain
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
new file mode 100644
index 00000000..4791c448
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
@@ -0,0 +1,398 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg_selector
+short_description: Manage site-local EPG selector in schema templates
+description:
+- Manage EPG selector in schema template on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: yes
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The type of the expression.
+ - The type is custom or is one of region, zone and ip_address
+ - The type can be zone only when the site is AWS.
+ required: true
+ type: str
+ aliases: [ tag ]
+ operator:
+ description:
+ - The operator associated to the expression.
+ - Operator has_key or does_not_have_key is only available for custom type / tag
+ required: true
+ type: str
+ choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ]
+ value:
+ description:
+ - The value associated to the expression.
+ - If the operator is in or not_in, the value should be a comma separated string.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a selector to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_site_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ site: Site 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec
+
+EXPRESSION_KEYS = {
+ 'ip_address': 'ipAddress',
+ 'region': 'region',
+ 'zone': 'zone',
+}
+
+EXPRESSION_OPERATORS = {
+ 'not_in': 'notIn',
+ 'not_equals': 'notEquals',
+ 'has_key': 'keyExist',
+ 'does_not_have_key': 'keyNotExist',
+ 'in': 'in',
+ 'equals': 'equals',
+}
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ selector=dict(type='str'),
+ expressions=dict(type='list', elements='dict', options=mso_expression_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['selector']],
+ ['state', 'present', ['selector']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ selector = module.params.get('selector')
+ expressions = module.params.get('expressions')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get cloud type
+ site_type = mso.get_obj('sites', name=site).get("cloudProviders")[0]
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ''
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj['sites'][site_idx]['anps']]
+ anps_in_temp = [a.get('name') for a in schema_obj['templates'][template_idx]['anps']]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps_in_temp)))
+ else:
+ # Get anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = '/sites/{0}/anps/-'.format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Get anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if 'anpRef' not in payload:
+ epgs = [e.get('epgRef') for e in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs']]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level?
+ if ('anpRef' not in payload and epg_ref not in epgs) or 'anpRef' in payload:
+ epgs_in_temp = [e.get('name') for e in schema_obj['templates'][template_idx]['anps'][template_anp_idx]['epgs']]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1}".format(epg, ', '.join(epgs_in_temp)))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if 'anpRef' not in payload:
+ op_path = '/sites/{0}/anps/{1}/epgs/-'.format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload['epgs'] = [new_epg]
+
+ # Get index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ # Get selectors
+ # If anp at site level and epg is at site level
+ if 'anpRef' not in payload and 'epgRef' not in payload:
+ if selector and " " in selector:
+ mso.fail_json(msg="There should not be any space in selector name.")
+ selectors = [s.get('name') for s in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors']]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = '/sites/{0}/anps/{1}/epgs/{2}/selectors/{3}'.format(site_template, anp, epg, selector_idx)
+ mso.existing = schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors'][selector_idx]
+
+ if state == 'query':
+ if 'anpRef' in payload:
+ mso.fail_json(msg="Anp '{anp}' does not exist in site level.".format(anp=anp))
+ if 'epgRef' in payload:
+ mso.fail_json(msg="Epg '{epg}' does not exist in site level.".format(epg=epg))
+ if selector is None:
+ mso.existing = schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors']
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=selector_path))
+ elif state == 'present':
+ # Get expressions
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type = expression.get('type')
+ operator = expression.get('operator')
+ value = expression.get('value')
+ if " " in type:
+ mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(type))
+ if operator in ["has_key", "does_not_have_key"] and value:
+ mso.fail_json(
+ msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, type))
+ if operator in ["not_in", "in", "equals", "not_equals"] and not value:
+ mso.fail_json(
+ msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, type))
+ if type in ["region", "zone", "ip_address"]:
+ if type == "zone" and site_type != "aws":
+ mso.fail_json(msg="Type 'zone' is only supported for aws")
+ if operator in ["has_key", "does_not_have_key"]:
+ mso.fail_json(msg="Operator '{0}' is not supported when expression type is '{1}'".format(operator, type))
+ type = EXPRESSION_KEYS.get(type)
+ else:
+ type = 'Custom:' + type
+ all_expressions.append(dict(
+ key=type,
+ operator=EXPRESSION_OPERATORS.get(operator),
+ value=value,
+ ))
+ new_selector = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ selectors_path = '/sites/{0}/anps/{1}/epgs/{2}/selectors/-'.format(site_template, anp, epg)
+
+ # if payload is empty, anp and epg already exist at site level
+ if not payload:
+ op_path = selectors_path
+ payload = new_selector
+ # if payload exist
+ else:
+ # if anp already exists at site level
+ if 'anpRef' not in payload:
+ payload['selectors'] = [new_selector]
+ else:
+ payload['epgs'][0]['selectors'] = [new_selector]
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=op_path, value=mso.sent))
+
+ mso.existing = new_selector
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
new file mode 100644
index 00000000..224af40d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
@@ -0,0 +1,264 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg_staticleaf
+short_description: Manage site-local EPG static leafs in schema template
+description:
+- Manage site-local EPG static leafs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: yes
+ pod:
+ description:
+ - The pod of the static leaf.
+ type: str
+ leaf:
+ description:
+ - The path of the static leaf.
+ type: str
+ aliases: [ name ]
+ vlan:
+ description:
+ - The VLAN id of the static leaf.
+ type: int
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new static leaf to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ vlan: 123
+ state: present
+ delegate_to: localhost
+
+- name: Remove a static leaf from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG static leaf
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ leaf: Leaf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG static leafs
+ cisco.mso.mso_schema_site_anp_epg_staticleaf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ pod=dict(type='str'), # This parameter is not required for querying all objects
+ leaf=dict(type='str', aliases=['name']),
+ vlan=dict(type='int'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['pod', 'leaf', 'vlan']],
+ ['state', 'present', ['pod', 'leaf', 'vlan']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ pod = module.params.get('pod')
+ leaf = module.params.get('leaf')
+ vlan = module.params.get('vlan')
+ state = module.params.get('state')
+
+ leafpath = 'topology/{0}/node-{1}'.format(pod, leaf)
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
+ if epg_ref not in epgs:
+ mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs)))
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Leaf
+ leafs = [(leaf.get('path'), leaf.get('portEncapVlan')) for leaf in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs']]
+ if (leafpath, vlan) in leafs:
+ leaf_idx = leafs.index((leafpath, vlan))
+ # FIXME: Changes based on index are DANGEROUS
+ leaf_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs/{3}'.format(site_template, anp, epg, leaf_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs'][leaf_idx]
+
+ if state == 'query':
+ if leaf is None or vlan is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticLeafs']
+ elif not mso.existing:
+ mso.fail_json(msg="Static leaf '{leaf}/{vlan}' not found".format(leaf=leaf, vlan=vlan))
+ mso.exit_json()
+
+ leafs_path = '/sites/{0}/anps/{1}/epgs/{2}/staticLeafs'.format(site_template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=leaf_path))
+
+ elif state == 'present':
+ payload = dict(
+ path=leafpath,
+ portEncapVlan=vlan,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=leaf_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=leafs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
new file mode 100644
index 00000000..89485df8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
@@ -0,0 +1,448 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg_staticport
+short_description: Manage site-local EPG static ports in schema template
+description:
+- Manage site-local EPG static ports in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: yes
+ type:
+ description:
+ - The path type of the static port
+ - vpc is used for a Virtual Port Channel
+ - dpc is used for a Direct Port Channel
+ - port is used for a single interface
+ type: str
+ choices: [ port, vpc, dpc ]
+ default: port
+ pod:
+ description:
+ - The pod of the static port.
+ type: str
+ leaf:
+ description:
+ - The leaf of the static port.
+ type: str
+ fex:
+ description:
+ - The fex id of the static port.
+ type: str
+ path:
+ description:
+ - The path of the static port.
+ type: str
+ vlan:
+ description:
+ - The port encap VLAN id of the static port.
+ type: int
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the static port.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ default: lazy
+ mode:
+ description:
+ - The mode of the static port.
+ - C(native) means B(Access (802.1p)).
+ - C(regular) means B(Trunk).
+ - C(untagged) means B(Access (untagged)).
+ type: str
+ choices: [ native, regular, untagged ]
+ default: untagged
+ primary_micro_segment_vlan:
+ description:
+ - Primary micro-seg VLAN of static port.
+ type: int
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing an object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new static port to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: immediate
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static fex port to a site EPG
+ mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ fex: 151
+ path: eth1/1
+ vlan: 126
+ deployment_immediacy: lazy
+ state: present
+ delegate_to: localhost
+
+- name: Add a new static VPC to a site EPG
+ mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ pod: pod-1
+ leaf: 101-102
+ path: ansible_polgrp
+ vlan: 127
+ type: vpc
+ mode: untagged
+ deployment_immediacy: lazy
+ state: present
+ delegate_to: localhost
+
+- name: Remove a static port from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG static port
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG static ports
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ type=dict(type='str', default='port', choices=['port', 'vpc', 'dpc']),
+ pod=dict(type='str'), # This parameter is not required for querying all objects
+ leaf=dict(type='str'), # This parameter is not required for querying all objects
+ fex=dict(type='str'), # This parameter is not required for querying all objects
+ path=dict(type='str'), # This parameter is not required for querying all objects
+ vlan=dict(type='int'), # This parameter is not required for querying all objects
+ primary_micro_segment_vlan=dict(type='int'), # This parameter is not required for querying all objects
+ deployment_immediacy=dict(type='str', default='lazy', choices=['immediate', 'lazy']),
+ mode=dict(type='str', default='untagged', choices=['native', 'regular', 'untagged']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['type', 'pod', 'leaf', 'path', 'vlan']],
+ ['state', 'present', ['type', 'pod', 'leaf', 'path', 'vlan']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ path_type = module.params.get('type')
+ pod = module.params.get('pod')
+ leaf = module.params.get('leaf')
+ fex = module.params.get('fex')
+ path = module.params.get('path')
+ vlan = module.params.get('vlan')
+ primary_micro_segment_vlan = module.params.get('primary_micro_segment_vlan')
+ deployment_immediacy = module.params.get('deployment_immediacy')
+ mode = module.params.get('mode')
+ state = module.params.get('state')
+
+ if path_type == 'port' and fex is not None:
+ # Select port path for fex if fex param is used
+ portpath = 'topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]'.format(pod, leaf, fex, path)
+ elif path_type == 'vpc':
+ portpath = 'topology/{0}/protpaths-{1}/pathep-[{2}]'.format(pod, leaf, path)
+ else:
+ portpath = 'topology/{0}/paths-{1}/pathep-[{2}]'.format(pod, leaf, path)
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ sites_list = [s.get('siteId') + '/' + s.get('templateName') for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ', '.join(sites_list)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ payload = dict()
+ ops = []
+ op_path = ''
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj['sites'][site_idx]['anps']]
+ anps_in_temp = [a.get('name') for a in schema_obj['templates'][template_idx]['anps']]
+ if anp not in anps_in_temp:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ else:
+ # Update anp index at template level
+ template_anp_idx = anps_in_temp.index(anp)
+
+ # If anp not at site level but exists at template level
+ if anp_ref not in anps:
+ op_path = '/sites/{0}/anps/-'.format(site_template)
+ payload.update(
+ anpRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ ),
+ )
+
+ else:
+ # Update anp index at site level
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+
+ # If anp exists at site level
+ if 'anpRef' not in payload:
+ epgs = [e.get('epgRef') for e in schema_obj['sites'][site_idx]['anps'][anp_idx]['epgs']]
+
+ # If anp already at site level AND if epg not at site level (or) anp not at site level
+ if ('anpRef' not in payload and epg_ref not in epgs) or 'anpRef' in payload:
+ epgs_in_temp = [e.get('name') for e in schema_obj['templates'][template_idx]['anps'][template_anp_idx]['epgs']]
+
+ # If EPG not at template level - Fail
+ if epg not in epgs_in_temp:
+ mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ', '.join(epgs_in_temp), epg_ref))
+
+ # EPG at template level but not at site level. Create payload at site level for EPG
+ else:
+
+ new_epg = dict(
+ epgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ anpName=anp,
+ epgName=epg,
+ )
+ )
+
+ # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
+ if 'anpRef' not in payload:
+ op_path = '/sites/{0}/anps/{1}/epgs/-'.format(site_template, anp)
+ payload = new_epg
+ else:
+ # If anp in payload, anp exists at site level. Update payload with EPG payload
+ payload['epgs'] = [new_epg]
+
+ # Update index of EPG at site level
+ else:
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Leaf
+ # If anp at site level and epg is at site level
+ if 'anpRef' not in payload and 'epgRef' not in payload:
+ portpaths = [p.get('path') for p in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts']]
+ if portpath in portpaths:
+ portpath_idx = portpaths.index(portpath)
+ port_path = '/sites/{0}/anps/{1}/epgs/{2}/staticPorts/{3}'.format(site_template, anp, epg, portpath_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts'][portpath_idx]
+
+ if state == 'query':
+ if leaf is None or vlan is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['staticPorts']
+ elif not mso.existing:
+ mso.fail_json(msg="Static port '{portpath}' not found".format(portpath=portpath))
+ mso.exit_json()
+
+ ports_path = '/sites/{0}/anps/{1}/epgs/{2}/staticPorts'.format(site_template, anp, epg)
+ ops = []
+ new_leaf = dict(
+ deploymentImmediacy=deployment_immediacy,
+ mode=mode,
+ path=portpath,
+ portEncapVlan=vlan,
+ type=path_type,
+ microSegVlan=primary_micro_segment_vlan,
+ )
+
+ # If payload is empty, anp and EPG already exist at site level
+ if not payload:
+ op_path = ports_path + '/-'
+ payload = new_leaf
+
+ # If payload exists
+ else:
+ # If anp already exists at site level
+ if 'anpRef' not in payload:
+ payload['staticPorts'] = [new_leaf]
+ else:
+ payload['epgs'][0]['staticPorts'] = [new_leaf]
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=port_path))
+
+ elif state == 'present':
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=port_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=op_path, value=mso.sent))
+
+ mso.existing = new_leaf
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
new file mode 100644
index 00000000..efc97c1e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
@@ -0,0 +1,293 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_anp_epg_subnet
+short_description: Manage site-local EPG subnets in schema template
+description:
+- Manage site-local EPG subnets in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG.
+ type: str
+ required: yes
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_anp_epg
+- module: cisco.mso.mso_schema_template_anp_epg_subnet
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new subnet to a site EPG
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site EPG subnet
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site EPG subnets
+ cisco.mso.mso_schema_site_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+ argument_spec.update(mso_subnet_spec())
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ subnet = module.params.get('subnet')
+ description = module.params.get('description')
+ scope = module.params.get('scope')
+ shared = module.params.get('shared')
+ no_default_gateway = module.params.get('no_default_gateway')
+ querier = module.params.get('querier')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get ANP
+ anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
+ anps = [a.get('anpRef') for a in schema_obj.get('sites')[site_idx]['anps']]
+ if anp_ref not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ anp_idx = anps.index(anp_ref)
+
+ # Get EPG
+ epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ epgs = [e.get('epgRef') for e in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs']]
+ if epg_ref not in epgs:
+ mso.fail_json(msg="Provided epg '{0}' does not exist. Existing epgs: {1}".format(epg, ', '.join(epgs)))
+ epg_idx = epgs.index(epg_ref)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/sites/{0}/anps/{1}/epgs/{2}/subnets/{3}'.format(site_template, anp, epg, subnet_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/sites/{0}/anps/{1}/epgs/{2}/subnets'.format(site_template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ if description is None:
+ description = subnet
+ if scope is None:
+ scope = 'private'
+ if shared is None:
+ shared = False
+ if no_default_gateway is None:
+ no_default_gateway = False
+ if querier is None:
+ querier = False
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ querier=querier,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
new file mode 100644
index 00000000..538e268b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_bd
+short_description: Manage site-local Bridge Domains (BDs) in schema template
+description:
+- Manage site-local BDs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ aliases: [ name ]
+ host_route:
+ description:
+ - Whether host-based routing is enabled.
+ type: bool
+ svi_mac:
+ description:
+ - SVI MAC Address
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_site_bd_l3out
+- module: cisco.mso.mso_schema_site_bd_subnet
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BDs
+ cisco.mso.mso_schema_site_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bd=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ host_route=dict(type='bool'),
+ svi_mac=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['bd']],
+ ['state', 'present', ['bd']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ bd = module.params.get('bd')
+ host_route = module.params.get('host_route')
+ svi_mac = module.params.get('svi_mac')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get BD
+ bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
+ bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
+ if bd is not None and bd_ref in bds:
+ bd_idx = bds.index(bd_ref)
+ bd_path = '/sites/{0}/bds/{1}'.format(site_template, bd)
+ mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]
+ mso.existing['bdRef'] = mso.dict_from_ref(mso.existing.get('bdRef'))
+
+ if state == 'query':
+ if bd is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['bds']
+ for bd in mso.existing:
+ bd['bdRef'] = mso.dict_from_ref(bd.get('bdRef'))
+ elif not mso.existing:
+ mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
+ mso.exit_json()
+
+ bds_path = '/sites/{0}/bds'.format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=bd_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ if host_route is None:
+ host_route = False
+
+ payload = dict(
+ bdRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ bdName=bd,
+ ),
+ hostBasedRouting=host_route,
+ )
+ if svi_mac is not None:
+ payload.update(mac=svi_mac)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=bd_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=bds_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
new file mode 100644
index 00000000..2a85860f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_bd_l3out
+short_description: Manage site-local BD l3out's in schema template
+description:
+- Manage site-local BDs l3out's in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ bd:
+ description:
+ - The name of the BD.
+ type: str
+ required: yes
+ aliases: [ name ]
+ l3out:
+ description:
+ - The name of the l3out.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_bd
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out: L3out1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out: L3out1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD l3out
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ l3out: L3out1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BD l3outs
+ cisco.mso.mso_schema_site_bd_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bd=dict(type='str', required=True),
+ l3out=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['l3out']],
+ ['state', 'present', ['l3out']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ bd = module.params.get('bd')
+ l3out = module.params.get('l3out')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get BD
+ bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
+ bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
+ if bd_ref not in bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ', '.join(bds)))
+ bd_idx = bds.index(bd_ref)
+
+ # Get L3out
+ l3outs = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs']
+ if l3out is not None and l3out in l3outs:
+ l3out_idx = l3outs.index(l3out)
+ # FIXME: Changes based on index are DANGEROUS
+ l3out_path = '/sites/{0}/bds/{1}/l3Outs/{2}'.format(site_template, bd, l3out_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs'][l3out_idx]
+
+ if state == 'query':
+ if l3out is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['l3Outs']
+ elif not mso.existing:
+ mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out))
+ mso.exit_json()
+
+ l3outs_path = '/sites/{0}/bds/{1}/l3Outs'.format(site_template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=l3out_path))
+
+ elif state == 'present':
+ mso.sent = l3out
+ if not mso.existing:
+ ops.append(dict(op='add', path=l3outs_path + '/-', value=l3out))
+
+ mso.existing = mso.sent
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
new file mode 100644
index 00000000..002c6d41
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_bd_subnet
+short_description: Manage site-local BD subnets in schema template
+description:
+- Manage site-local BD subnets in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ bd:
+ description:
+ - The name of the BD.
+ type: str
+ required: true
+ aliases: [ name ]
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ is_virtual_ip:
+ description:
+ - Treat as Virtual IP Address
+ type: bool
+ default: false
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_bd
+- module: cisco.mso.mso_schema_template_bd
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site BD subnet
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ subnet: 11.11.11.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site BD subnets
+ cisco.mso.mso_schema_site_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(mso_subnet_spec())
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bd=dict(type='str', aliases=['name'], required=True),
+ subnet=dict(type='str', aliases=['ip']),
+ description=dict(type='str'),
+ scope=dict(type='str', default='private', choices=['private', 'public']),
+ shared=dict(type='bool', default=False),
+ no_default_gateway=dict(type='bool', default=False),
+ querier=dict(type='bool', default=False),
+ is_virtual_ip=dict(type='bool', default=False),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ bd = module.params.get('bd')
+ subnet = module.params.get('subnet')
+ description = module.params.get('description')
+ scope = module.params.get('scope')
+ shared = module.params.get('shared')
+ no_default_gateway = module.params.get('no_default_gateway')
+ querier = module.params.get('querier')
+ is_virtual_ip = module.params.get('is_virtual_ip')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get template BDs
+ template_bds = [b.get('name') for b in schema_obj.get('templates')[template_idx]['bds']]
+
+ # Get template BD
+ if bd not in template_bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing template BDs: {1}".format(bd, ', '.join(template_bds)))
+ template_bd_idx = template_bds.index(bd)
+ template_bd = schema_obj.get('templates')[template_idx]['bds'][template_bd_idx]
+ if template_bd.get('l2Stretch') is True and state == 'present':
+ mso.fail_json(
+ msg="The l2Stretch of template bd should be false in order to create a site bd subnet. Set l2Stretch as false using mso_schema_template_bd"
+ )
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get BD
+ bd_ref = mso.bd_ref(schema_id=schema_id, template=template, bd=bd)
+ bds = [v.get('bdRef') for v in schema_obj.get('sites')[site_idx]['bds']]
+ if bd_ref not in bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing site BDs: {1}".format(bd, ', '.join(bds)))
+ bd_idx = bds.index(bd_ref)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets']]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/sites/{0}/bds/{1}/subnets/{2}'.format(site_template, bd, subnet_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['bds'][bd_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/sites/{0}/bds/{1}/subnets'.format(site_template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ if description is None:
+ description = subnet
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ virtual=is_virtual_ip,
+ querier=querier,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
new file mode 100644
index 00000000..ad83c069
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_external_epg_selector
+short_description: Manage External EPG selector in schema of cloud sites
+description:
+- Manage External EPG selector in schema of cloud sites on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the External EPG to be managed.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the cloud site.
+ type: str
+ required: yes
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression which in this case is always IP address.
+ required: true
+ type: str
+ choices: [ ip_address ]
+ operator:
+ description:
+ - The operator associated with the expression which in this case is always equals.
+ required: true
+ type: str
+ choices: [ equals ]
+ value:
+ description:
+ - The value of the IP Address / Subnet associated with the expression.
+ required: true
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a selector to an External EPG
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: test
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: test
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_site_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_test
+ template: Template1
+ site: azure_ansible_test
+ external_epg: ext1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ external_epg=dict(type='str', required=True),
+ selector=dict(type='str'),
+ expressions=dict(type='list', elements='dict', options=mso_expression_spec_ext_epg()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ site = module.params.get('site')
+ external_epg = module.params.get('external_epg')
+ selector = module.params.get('selector')
+ expressions = module.params.get('expressions')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ sites_list = [s.get('siteId') + '/' + s.get('templateName') for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ', '.join(sites_list)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ payload = dict()
+ op_path = ''
+
+ # Get External EPG
+ ext_epg_ref = mso.ext_epg_ref(schema_id=schema_id, template=template, external_epg=external_epg)
+ external_epgs = [e.get('externalEpgRef') for e in schema_obj.get('sites')[site_idx]['externalEpgs']]
+
+ if ext_epg_ref not in external_epgs:
+ op_path = '/sites/{0}/externalEpgs/-'.format(site_template)
+ payload = dict(
+ externalEpgRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ externalEpgName=external_epg,
+ ),
+ l3outDn='',
+ )
+
+ else:
+ external_epg_idx = external_epgs.index(ext_epg_ref)
+
+ # Get Selector
+ selectors = [s.get('name') for s in schema_obj['sites'][site_idx]['externalEpgs'][external_epg_idx]['subnets']]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = '/sites/{0}/externalEpgs/{1}/subnets/{2}'.format(site_template, external_epg, selector_idx)
+ mso.existing = schema_obj['sites'][site_idx]['externalEpgs'][external_epg_idx]['subnets'][selector_idx]
+
+ selectors_path = '/sites/{0}/externalEpgs/{1}/subnets/-'.format(site_template, external_epg)
+ ops = []
+
+ if state == 'query':
+ if selector is None:
+ mso.existing = schema_obj['sites'][site_idx]['externalEpgs'][external_epg_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=selector_path))
+
+ elif state == 'present':
+ # Get expressions
+ types = dict(ip_address='ipAddress')
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type_val = expression.get('type')
+ operator = expression.get('operator')
+ value = expression.get('value')
+ all_expressions.append(dict(
+ key=types.get(type_val),
+ operator=operator,
+ value=value,
+ ))
+ else:
+ mso.fail_json(msg="Missing expressions in selector")
+
+ subnets = dict(
+ name=selector,
+ ip=all_expressions[0]['value']
+ )
+
+ if not external_epgs:
+ payload['subnets'] = [subnets]
+ else:
+ payload = subnets
+ op_path = selectors_path
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=op_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
new file mode 100644
index 00000000..8846bc4c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
@@ -0,0 +1,213 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_vrf
+short_description: Manage site-local VRFs in schema template
+description:
+- Manage site-local VRFs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF to manage.
+ type: str
+ aliases: [ name ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRFs
+ cisco.mso.mso_schema_site_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['vrf']],
+ ['state', 'present', ['vrf']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/template '{0}-{1}' does not exist. Existing sites/templates: {2}".format(site, template, ', '.join(sites)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
+ if vrf is not None and vrf_ref in vrfs:
+ vrf_idx = vrfs.index(vrf_ref)
+ vrf_path = '/sites/{0}/vrfs/{1}'.format(site_template, vrf)
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]
+
+ if state == 'query':
+ if vrf is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs']
+ elif not mso.existing:
+ mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
+ mso.exit_json()
+
+ vrfs_path = '/sites/{0}/vrfs'.format(site_template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=vrf_path))
+
+ elif state == 'present':
+ payload = dict(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=vrf_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=vrfs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
new file mode 100644
index 00000000..dc68a836
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
@@ -0,0 +1,239 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_vrf_region
+short_description: Manage site-local VRF regions in schema template
+description:
+- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: yes
+ region:
+ description:
+ - The name of the region to manage.
+ type: str
+ aliases: [ name ]
+ vpn_gateway_router:
+ description:
+ - Whether VPN Gateway Router is enabled or not.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API, this module cannot create empty region (i.e. regions without cidrs)
+ Use the M(cisco.mso.mso_schema_site_vrf_region_cidr) to automatically create regions with cidrs.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Remove VPN Gateway Router at site VRF Region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ vpn_gateway_router: false
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF regions
+ cisco.mso.mso_schema_site_vrf_region:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', required=True),
+ region=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ vpn_gateway_router=dict(type='bool'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['region']],
+ ['state', 'present', ['region']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ region = module.params.get('region')
+ vpn_gateway_router = module.params.get('vpn_gateway_router')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
+ vrfs_name = [mso.dict_from_ref(v).get('vrfName') for v in vrfs]
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(vrfs_name)))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
+ if region is not None and region in regions:
+ region_idx = regions.index(region)
+ region_path = '/sites/{0}/vrfs/{1}/regions/{2}'.format(site_template, vrf, region)
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]
+
+ if state == 'query':
+ if region is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']
+ elif not mso.existing:
+ mso.fail_json(msg="Region '{region}' not found".format(region=region))
+ mso.exit_json()
+
+ regions_path = '/sites/{0}/vrfs/{1}/regions'.format(site_template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=region_path))
+
+ elif state == 'present':
+
+ payload = dict(
+ name=region,
+ isVpnGatewayRouter=vpn_gateway_router,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=region_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=regions_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
new file mode 100644
index 00000000..324209a9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_vrf_region_cidr
+short_description: Manage site-local VRF region CIDRs in schema template
+description:
+- Manage site-local VRF region CIDRs in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Lionel Hercot (@lhercot)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: yes
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: yes
+ cidr:
+ description:
+ - The name of the region CIDR to manage.
+ type: str
+ aliases: [ ip ]
+ primary:
+ description:
+ - Whether this is the primary CIDR.
+ type: bool
+ default: true
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region
+- module: cisco.mso.mso_schema_site_vrf_region_cidr_subnet
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF region CIDR
+ cisco.mso.mso_schema_site_vrf_region_cidr:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', required=True),
+ region=dict(type='str', required=True),
+ cidr=dict(type='str', aliases=['ip']), # This parameter is not required for querying all objects
+ primary=dict(type='bool', default=True),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['cidr']],
+ ['state', 'present', ['cidr']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ region = module.params.get('region')
+ cidr = module.params.get('cidr')
+ primary = module.params.get('primary')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ payload = dict()
+ op_path = ''
+ new_cidr = dict(
+ ip=cidr,
+ primary=primary,
+ )
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ all_sites = schema_obj.get('sites')
+ sites = []
+ if all_sites is not None:
+ sites = [(s.get('siteId'), s.get('templateName')) for s in all_sites]
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ template_vrfs = [a.get('name') for a in schema_obj['templates'][template_idx]['vrfs']]
+ if vrf not in template_vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(template_vrfs)))
+
+ # if site-template does not exist, create it
+ if (site_id, template) not in sites:
+ op_path = '/sites/-'
+ payload.update(
+ siteId=site_id,
+ templateName=template,
+ vrfs=[dict(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ regions=[dict(
+ name=region,
+ cidrs=[new_cidr]
+ )]
+ )]
+ )
+
+ else:
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # If vrf not at site level but exists at template level
+ vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
+ if vrf_ref not in vrfs:
+ op_path = '/sites/{0}/vrfs/-'.format(site_template)
+ payload.update(
+ vrfRef=dict(
+ schemaId=schema_id,
+ templateName=template,
+ vrfName=vrf,
+ ),
+ regions=[dict(
+ name=region,
+ cidrs=[new_cidr]
+ )]
+ )
+ else:
+ # Update vrf index at site level
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
+ if region not in regions:
+ op_path = '/sites/{0}/vrfs/{1}/regions/-'.format(site_template, vrf)
+ payload.update(
+ name=region,
+ cidrs=[new_cidr]
+ )
+ else:
+ region_idx = regions.index(region)
+
+ # Get CIDR
+ cidrs = [c.get('ip') for c in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']]
+ if cidr is not None:
+ if cidr in cidrs:
+ cidr_idx = cidrs.index(cidr)
+ # FIXME: Changes based on index are DANGEROUS
+ cidr_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}'.format(site_template, vrf, region, cidr_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]
+ op_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/-'.format(site_template, vrf, region)
+ payload = new_cidr
+
+ if state == 'query':
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+ elif vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist at site level.".format(vrf))
+ elif not regions or region not in regions:
+ mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ', '.join(regions)))
+ elif cidr is None and not payload:
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']
+ elif not mso.existing:
+ mso.fail_json(msg="CIDR IP '{cidr}' not found".format(cidr=cidr))
+ mso.exit_json()
+
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=cidr_path))
+
+ elif state == 'present':
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=cidr_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=op_path, value=mso.sent))
+
+ mso.existing = new_cidr
+
+ if not module.check_mode and mso.previous != mso.existing:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
new file mode 100644
index 00000000..cd019d53
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_vrf_region_cidr_subnet
+short_description: Manage site-local VRF regions in schema template
+description:
+- Manage site-local VRF regions in schema template on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Lionel Hercot (@lhercot)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: yes
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: yes
+ cidr:
+ description:
+ - The IP range of for the region CIDR.
+ type: str
+ required: yes
+ subnet:
+ description:
+ - The IP subnet of this region CIDR.
+ type: str
+ aliases: [ ip ]
+ zone:
+ description:
+ - The name of the zone for the region CIDR subnet.
+ - This argument is required for AWS sites.
+ type: str
+ aliases: [ name ]
+ vgw:
+ description:
+ - Whether this subnet is used for the Azure Gateway in Azure.
+ - Whether this subnet is used for the Transit Gateway Attachment in AWS.
+ type: bool
+ aliases: [ hub_network ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region_cidr
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ zone: us-west-1a
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ subnet: 14.14.14.2/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all site VRF region CIDR subnet
+ cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ cidr: 14.14.14.1/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', required=True),
+ region=dict(type='str', required=True),
+ cidr=dict(type='str', required=True),
+ subnet=dict(type='str', aliases=['ip']), # This parameter is not required for querying all objects
+ zone=dict(type='str', aliases=['name']),
+ vgw=dict(type='bool', aliases=['hub_network']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ region = module.params.get('region')
+ cidr = module.params.get('cidr')
+ subnet = module.params.get('subnet')
+ zone = module.params.get('zone')
+ vgw = module.params.get('vgw')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ sites_list = [s.get('siteId') + '/' + s.get('templateName') for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
+ "Existing siteIds/templates: {3}".format(site, site_id, template, ', '.join(sites_list)))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
+
+ # If vrf not at site level but exists at template level
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist at site level."
+ " Use mso_schema_site_vrf_region_cidr to create it.".format(vrf))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
+ if region not in regions:
+ mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}."
+ " Use mso_schema_site_vrf_region_cidr to create it.".format(region, ', '.join(regions)))
+ region_idx = regions.index(region)
+
+ # Get CIDR
+ cidrs = [c.get('ip') for c in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs']]
+ if cidr not in cidrs:
+ mso.fail_json(msg="Provided CIDR IP '{0}' does not exist. Existing CIDR IPs: {1}."
+ " Use mso_schema_site_vrf_region_cidr to create it.".format(cidr, ', '.join(cidrs)))
+ cidr_idx = cidrs.index(cidr)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets']]
+ if subnet is not None and subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets/{4}'.format(site_template, vrf, region, cidr_idx, subnet_idx)
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]['cidrs'][cidr_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/sites/{0}/vrfs/{1}/regions/{2}/cidrs/{3}/subnets'.format(site_template, vrf, region, cidr_idx)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ payload = dict(
+ ip=subnet,
+ zone=""
+ )
+
+ if zone is not None:
+ payload['zone'] = zone
+ if vgw is True:
+ payload['usage'] = 'gateway'
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
new file mode 100644
index 00000000..5e5fcc92
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
@@ -0,0 +1,251 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_site_vrf_region_hub_network
+short_description: Manage site-local VRF region hub network in schema template
+description:
+- Manage site-local VRF region hub network in schema template on Cisco ACI Multi-Site.
+- The 'Hub Network' feature was introduced in Multi-Site Orchestrator (MSO) version 3.0(1) for AWS and version 3.0(2) for Azure.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: yes
+ region:
+ description:
+ - The name of the region.
+ type: str
+ required: yes
+ hub_network:
+ description:
+ - The hub network to be managed.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the hub network.
+ - The hub-default is the default created hub network.
+ type: str
+ required: yes
+ tenant:
+ description:
+ - The tenant name of the hub network.
+ type: str
+ required: yes
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- The ACI MultiSite PATCH API has a deficiency requiring some objects to be referenced by index.
+ This can cause silent corruption on concurrent access when changing/removing on object as
+ the wrong object may be referenced. This module is affected by this deficiency.
+seealso:
+- module: cisco.mso.mso_schema_site_vrf_region
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ hub_network:
+ name: hub-default
+ tenant: infra
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query site VRF region hub network
+ cisco.mso.mso_schema_site_vrf_region_hub_network:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ vrf: VRF1
+ region: us-west-1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_hub_network_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ site=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', required=True),
+ region=dict(type='str', required=True),
+ hub_network=dict(type='dict', options=mso_hub_network_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'present', ['hub_network']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ site = module.params.get('site')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ region = module.params.get('region')
+ hub_network = module.params.get('hub_network')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+ schema_id = schema_obj.get('id')
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+
+ # Get site
+ site_id = mso.lookup_site(site)
+
+ # Get site_idx
+ if 'sites' not in schema_obj:
+ mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
+ sites = [(s.get('siteId'), s.get('templateName')) for s in schema_obj.get('sites')]
+ if (site_id, template) not in sites:
+ mso.fail_json(msg="Provided site-template association '{0}-{1}' does not exist.".format(site, template))
+
+ # Schema-access uses indexes
+ site_idx = sites.index((site_id, template))
+ # Path-based access uses site_id-template
+ site_template = '{0}-{1}'.format(site_id, template)
+
+ # Get VRF
+ vrf_ref = mso.vrf_ref(schema_id=schema_id, template=template, vrf=vrf)
+ vrfs = [v.get('vrfRef') for v in schema_obj.get('sites')[site_idx]['vrfs']]
+ vrfs_name = [mso.dict_from_ref(v).get('vrfName') for v in vrfs]
+ if vrf_ref not in vrfs:
+ mso.fail_json(msg="Provided vrf '{0}' does not exist. Existing vrfs: {1}".format(vrf, ', '.join(vrfs_name)))
+ vrf_idx = vrfs.index(vrf_ref)
+
+ # Get Region
+ regions = [r.get('name') for r in schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions']]
+ if region not in regions:
+ mso.fail_json(msg="Provided region '{0}' does not exist. Existing regions: {1}".format(region, ', '.join(regions)))
+ region_idx = regions.index(region)
+ # Get Region object
+ region_obj = schema_obj.get('sites')[site_idx]['vrfs'][vrf_idx]['regions'][region_idx]
+ region_path = '/sites/{0}/vrfs/{1}/regions/{2}'.format(site_template, vrf, region)
+
+ # Get hub network
+ existing_hub_network = region_obj.get('cloudRsCtxProfileToGatewayRouterP')
+ if existing_hub_network is not None:
+ mso.existing = existing_hub_network
+
+ if state == 'query':
+ if not mso.existing:
+ mso.fail_json(msg="Hub network not found")
+ mso.exit_json()
+
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=region_path + '/cloudRsCtxProfileToGatewayRouterP'))
+ ops.append(dict(op='replace', path=region_path + '/isTGWAttachment', value=False))
+
+ elif state == 'present':
+ new_hub_network = dict(
+ name=hub_network.get('name'),
+ tenantName=hub_network.get('tenant'),
+ )
+ payload = region_obj
+ payload.update(
+ cloudRsCtxProfileToGatewayRouterP=new_hub_network,
+ isTGWAttachment=True,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op='replace', path=region_path, value=mso.sent))
+
+ mso.existing = new_hub_network
+
+ if not module.check_mode and mso.previous != mso.existing:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
new file mode 100644
index 00000000..62169443
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
@@ -0,0 +1,244 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template
+short_description: Manage templates in schemas
+description:
+- Manage templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ tenant:
+ description:
+ - The tenant used for this template.
+ type: str
+ required: yes
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API this module creates schemas when needed, and removes them when the last template has been removed.
+seealso:
+- module: cisco.mso.mso_schema
+- module: cisco.mso.mso_schema_site
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new template to a schema
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a template from a schema
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a template
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all templates
+ cisco.mso.mso_schema_template:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: Tenant 1
+ schema: Schema 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type='str', required=True),
+ schema=dict(type='str', required=True),
+ template=dict(type='str', aliases=['name']),
+ display_name=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['template']],
+ ['state', 'present', ['template']],
+ ],
+ )
+
+ tenant = module.params.get('tenant')
+ schema = module.params.get('schema')
+ template = module.params.get('template')
+ if template is not None:
+ template = template.replace(' ', '')
+ display_name = module.params.get('display_name')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+
+ mso.existing = {}
+ if schema_obj:
+ # Schema exists
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template:
+ if template in templates:
+ template_idx = templates.index(template)
+ mso.existing = schema_obj.get('templates')[template_idx]
+ else:
+ mso.existing = schema_obj.get('templates')
+ else:
+ schema_path = 'schemas'
+
+ if state == 'query':
+ if not mso.existing:
+ if template:
+ mso.fail_json(msg="Template '{0}' not found".format(template))
+ else:
+ mso.existing = []
+ mso.exit_json()
+
+ template_path = '/templates/{0}'.format(template)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ mso.proposed = mso.sent = {}
+ if not schema_obj:
+ # There was no schema to begin with
+ pass
+ elif len(templates) == 1 and mso.existing:
+ # There is only one tenant, remove schema
+ mso.existing = {}
+ if not module.check_mode:
+ mso.request(schema_path, method='DELETE')
+ elif mso.existing:
+ # Remove existing template
+ mso.existing = {}
+ ops.append(dict(op='remove', path=template_path))
+
+ elif state == 'present':
+ tenant_id = mso.lookup_tenant(tenant)
+
+ if display_name is None:
+ display_name = mso.existing.get('displayName', template)
+
+ if not schema_obj:
+ # Schema does not exist, so we have to create it
+ payload = dict(
+ displayName=schema,
+ templates=[dict(
+ name=template,
+ displayName=display_name,
+ tenantId=tenant_id,
+ )],
+ sites=[],
+ )
+
+ mso.existing = payload.get('templates')[0]
+
+ if not module.check_mode:
+ mso.request(schema_path, method='POST', data=payload)
+
+ elif mso.existing:
+ # Template exists, so we have to update it
+ payload = dict(
+ name=template,
+ displayName=display_name,
+ tenantId=tenant_id,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op='replace', path=template_path + '/displayName', value=display_name))
+ ops.append(dict(op='replace', path=template_path + '/tenantId', value=tenant_id))
+
+ mso.existing = mso.proposed
+ else:
+ # Template does not exist, so we have to add it
+ payload = dict(
+ name=template,
+ displayName=display_name,
+ tenantId=tenant_id,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ ops.append(dict(op='add', path='/templates/-', value=payload))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
new file mode 100644
index 00000000..f2679ce6
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
@@ -0,0 +1,207 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_anp
+short_description: Manage Application Network Profiles (ANPs) in schema templates
+description:
+- Manage ANPs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new ANP
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an ANP
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific ANPs
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all ANPs
+ cisco.mso.mso_schema_template_anp:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['anp']],
+ ['state', 'present', ['anp']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ display_name = module.params.get('display_name')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
+
+ if anp is not None and anp in anps:
+ anp_idx = anps.index(anp)
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]
+
+ if state == 'query':
+ if anp is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['anps']
+ elif not mso.existing:
+ mso.fail_json(msg="ANP '{anp}' not found".format(anp=anp))
+ mso.exit_json()
+
+ anps_path = '/templates/{0}/anps'.format(template)
+ anp_path = '/templates/{0}/anps/{1}'.format(template, anp)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=anp_path))
+
+ elif state == 'present':
+
+ if display_name is None and not mso.existing:
+ display_name = anp
+
+ epgs = []
+ if mso.existing:
+ epgs = None
+
+ payload = dict(
+ name=anp,
+ displayName=display_name,
+ epgs=epgs,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if display_name is not None:
+ ops.append(dict(op='replace', path=anp_path + '/displayName', value=display_name))
+ else:
+ ops.append(dict(op='add', path=anps_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if 'anpRef' in mso.previous:
+ del mso.previous['anpRef']
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
new file mode 100644
index 00000000..6883e4dd
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
@@ -0,0 +1,403 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_anp_epg
+short_description: Manage Endpoint Groups (EPGs) in schema templates
+description:
+- Manage EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+# contracts:
+# description:
+# - A list of contracts associated to this ANP.
+# type: list
+ bd:
+ description:
+ - The BD associated to this ANP.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the BD to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this ANP.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ type: str
+ subnets:
+ description:
+ - The subnets associated to this ANP.
+ type: list
+ elements: dict
+ suboptions:
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ useg_epg:
+ description:
+ - Whether this is a USEG EPG.
+ type: bool
+# useg_epg_attributes:
+# description:
+# - A dictionary consisting of USEG attributes.
+# type: dict
+ intra_epg_isolation:
+ description:
+ - Whether intra EPG isolation is enforced.
+ - When not specified, this parameter defaults to C(unenforced).
+ type: str
+ choices: [ enforced, unenforced ]
+ intersite_multicast_source:
+ description:
+ - Whether intersite multicast source is enabled.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ proxy_arp:
+ description:
+ - Whether proxy arp is enabled.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ preferred_group:
+ description:
+ - Whether this EPG is added to preferred group or not.
+ - When not specified, this parameter defaults to C(no).
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp
+- module: cisco.mso.mso_schema_template_anp_epg_subnet
+- module: cisco.mso.mso_schema_template_bd
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new EPG with preferred group.
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: present
+ preferred_group: yes
+ delegate_to: localhost
+
+- name: Remove an EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific EPG
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all EPGs
+ cisco.mso.mso_schema_template_anp_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ bd:
+ name: bd1
+ vrf:
+ name: vrf1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ bd=dict(type='dict', options=mso_reference_spec()),
+ vrf=dict(type='dict', options=mso_reference_spec()),
+ display_name=dict(type='str'),
+ useg_epg=dict(type='bool'),
+ intra_epg_isolation=dict(type='str', choices=['enforced', 'unenforced']),
+ intersite_multicast_source=dict(type='bool'),
+ proxy_arp=dict(type='bool'),
+ subnets=dict(type='list', elements='dict', options=mso_subnet_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ preferred_group=dict(type='bool'),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['epg']],
+ ['state', 'present', ['epg']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ display_name = module.params.get('display_name')
+ bd = module.params.get('bd')
+ if bd is not None and bd.get('template') is not None:
+ bd['template'] = bd.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ if vrf is not None and vrf.get('template') is not None:
+ vrf['template'] = vrf.get('template').replace(' ', '')
+ useg_epg = module.params.get('useg_epg')
+ intra_epg_isolation = module.params.get('intra_epg_isolation')
+ intersite_multicast_source = module.params.get('intersite_multicast_source')
+ proxy_arp = module.params.get('proxy_arp')
+ subnets = module.params.get('subnets')
+ state = module.params.get('state')
+ preferred_group = module.params.get('preferred_group')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if schema_obj:
+ schema_id = schema_obj.get('id')
+ else:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
+ if epg is not None and epg in epgs:
+ epg_idx = epgs.index(epg)
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]
+
+ if state == 'query':
+ if epg is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']
+ elif not mso.existing:
+ mso.fail_json(msg="EPG '{epg}' not found".format(epg=epg))
+
+ if 'bdRef' in mso.existing:
+ mso.existing['bdRef'] = mso.dict_from_ref(mso.existing['bdRef'])
+ if 'vrfRef' in mso.existing:
+ mso.existing['vrfRef'] = mso.dict_from_ref(mso.existing['vrfRef'])
+ mso.exit_json()
+
+ epgs_path = '/templates/{0}/anps/{1}/epgs'.format(template, anp)
+ epg_path = '/templates/{0}/anps/{1}/epgs/{2}'.format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=epg_path))
+
+ elif state == 'present':
+ bd_ref = mso.make_reference(bd, 'bd', schema_id, template)
+ vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
+ mso.stdout = str(subnets)
+ subnets = mso.make_subnets(subnets)
+
+ if display_name is None and not mso.existing:
+ display_name = epg
+
+ payload = dict(
+ name=epg,
+ displayName=display_name,
+ uSegEpg=useg_epg,
+ intraEpg=intra_epg_isolation,
+ mCastSource=intersite_multicast_source,
+ proxyArp=proxy_arp,
+ # FIXME: Missing functionality
+ # uSegAttrs=[],
+ subnets=subnets,
+ bdRef=bd_ref,
+ preferredGroup=preferred_group,
+ vrfRef=vrf_ref,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # Clean contractRef to fix api issue
+ for contract in mso.sent.get('contractRelationships'):
+ contract['contractRef'] = mso.dict_from_ref(contract.get('contractRef'))
+ ops.append(dict(op='replace', path=epg_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=epgs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if 'epgRef' in mso.previous:
+ del mso.previous['epgRef']
+ if 'bdRef' in mso.previous and mso.previous['bdRef'] != '':
+ mso.previous['bdRef'] = mso.dict_from_ref(mso.previous['bdRef'])
+ if 'vrfRef' in mso.previous and mso.previous['bdRef'] != '':
+ mso.previous['vrfRef'] = mso.dict_from_ref(mso.previous['vrfRef'])
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
new file mode 100644
index 00000000..cdf9692f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
@@ -0,0 +1,266 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_anp_epg_contract
+short_description: Manage EPG contracts in schema templates
+description:
+- Manage EPG contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: yes
+ contract:
+ description:
+ - A contract associated to this EPG.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp_epg
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a contract to an EPG
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ contract=dict(type='dict', options=mso_contractref_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['contract']],
+ ['state', 'present', ['contract']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ contract = module.params.get('contract')
+ if contract is not None and contract.get('template') is not None:
+ contract['template'] = contract.get('template').replace(' ', '')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ if contract:
+ if contract.get('schema') is None:
+ contract['schema'] = schema
+ contract['schema_id'] = mso.lookup_schema(contract.get('schema'))
+ if contract.get('template') is None:
+ contract['template'] = template
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ', '.join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=', '.join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Contract
+ if contract:
+ contracts = [(c.get('contractRef'),
+ c.get('relationshipType')) for c in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships']]
+ contract_ref = mso.contract_ref(**contract)
+ if (contract_ref, contract.get('type')) in contracts:
+ contract_idx = contracts.index((contract_ref, contract.get('type')))
+ contract_path = '/templates/{0}/anps/{1}/epgs/{2}/contractRelationships/{3}'.format(template, anp, epg, contract_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships'][contract_idx]
+
+ if state == 'query':
+ if not contract:
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['contractRelationships']
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
+
+ if 'contractRef' in mso.existing:
+ mso.existing['contractRef'] = mso.dict_from_ref(mso.existing.get('contractRef'))
+ mso.exit_json()
+
+ contracts_path = '/templates/{0}/anps/{1}/epgs/{2}/contractRelationships'.format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=contract_path))
+
+ elif state == 'present':
+ payload = dict(
+ relationshipType=contract.get('type'),
+ contractRef=dict(
+ contractName=contract.get('name'),
+ templateName=contract.get('template'),
+ schemaId=contract.get('schema_id'),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=contracts_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if 'contractRef' in mso.previous:
+ mso.previous['contractRef'] = mso.dict_from_ref(mso.previous.get('contractRef'))
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
new file mode 100644
index 00000000..d77c197d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
@@ -0,0 +1,281 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_anp_epg_selector
+short_description: Manage EPG selector in schema templates
+description:
+- Manage EPG selector in schema templates on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: yes
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression.
+ required: true
+ type: str
+ aliases: [ tag ]
+ operator:
+ description:
+ - The operator associated to the expression.
+ required: true
+ type: str
+ choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ]
+ value:
+ description:
+ - The value associated to the expression.
+ - If the operator is in or not_in, the value should be a comma separated str.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_anp_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a selector to an EPG
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ expressions:
+ - type: expression_1
+ operator: in
+ value: test
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_template_anp_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec
+
+EXPRESSION_KEYS = {
+ 'not_in': 'notIn',
+ 'not_equals': 'notEquals',
+ 'has_key': 'keyExist',
+ 'does_not_have_key': 'keyNotExist',
+ 'in': 'in',
+ 'equals': 'equals',
+}
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ selector=dict(type='str'),
+ expressions=dict(type='list', elements='dict', options=mso_expression_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['selector']],
+ ['state', 'present', ['selector']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ selector = module.params.get('selector')
+ expressions = module.params.get('expressions')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=', '.join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=', '.join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Selector
+ if selector and " " in selector:
+ mso.fail_json(msg="There should not be any space in selector name.")
+ selectors = [s.get('name') for s in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors']]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = '/templates/{0}/anps/{1}/epgs/{2}/selectors/{3}'.format(template, anp, epg, selector_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors'][selector_idx]
+
+ if state == 'query':
+ if selector is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['selectors']
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ selectors_path = '/templates/{0}/anps/{1}/epgs/{2}/selectors/-'.format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=selector_path))
+
+ elif state == 'present':
+ # Get expressions
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ tag = expression.get('type')
+ operator = expression.get('operator')
+ value = expression.get('value')
+ if " " in tag:
+ mso.fail_json(msg="There should not be any space in 'type' attribute of expression '{0}'".format(tag))
+ if operator in ["has_key", "does_not_have_key"] and value:
+ mso.fail_json(
+ msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, tag))
+ if operator in ["not_in", "in", "equals", "not_equals"] and not value:
+ mso.fail_json(
+ msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, tag))
+ all_expressions.append(dict(
+ key='Custom:' + tag,
+ operator=EXPRESSION_KEYS.get(operator),
+ value=value,
+ ))
+
+ payload = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=selectors_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
new file mode 100644
index 00000000..109827ea
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
@@ -0,0 +1,266 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_anp_epg_subnet
+short_description: Manage EPG subnets in schema templates
+description:
+- Manage EPG subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the ANP.
+ type: str
+ required: yes
+ epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: yes
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new subnet to an EPG
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from an EPG
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific EPG subnet
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all EPGs subnets
+ cisco.mso.mso_schema_template_anp_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_subnet_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ anp=dict(type='str', required=True),
+ epg=dict(type='str', required=True),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+ argument_spec.update(mso_subnet_spec())
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ epg = module.params.get('epg')
+ subnet = module.params.get('subnet')
+ description = module.params.get('description')
+ scope = module.params.get('scope')
+ shared = module.params.get('shared')
+ no_default_gateway = module.params.get('no_default_gateway')
+ querier = module.params.get('querier')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ anps = [a.get('name') for a in schema_obj.get('templates')[template_idx]['anps']]
+ if anp not in anps:
+ mso.fail_json(msg="Provided anp '{anp}' does not exist. Existing anps: {anps}".format(anp=anp, anps=', '.join(anps)))
+ anp_idx = anps.index(anp)
+
+ # Get EPG
+ epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs']]
+ if epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=', '.join(epgs)))
+ epg_idx = epgs.index(epg)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/templates/{0}/anps/{1}/epgs/{2}/subnets/{3}'.format(template, anp, epg, subnet_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['anps'][anp_idx]['epgs'][epg_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/templates/{0}/anps/{1}/epgs/{2}/subnets'.format(template, anp, epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ if description is None:
+ description = subnet
+ if scope is None:
+ scope = 'private'
+ if shared is None:
+ shared = False
+ if no_default_gateway is None:
+ no_default_gateway = False
+ if querier is None:
+ querier = False
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ querier=querier,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
new file mode 100644
index 00000000..0e7d0ff0
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
@@ -0,0 +1,444 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_bd
+short_description: Manage Bridge Domains (BDs) in schema templates
+description:
+- Manage BDs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+- Shreyas Srish (@shrsr)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ - Display Name of template for operations can only be used in some versions of mso.
+ - Use the name of template instead of Display Name to avoid discrepency.
+ type: str
+ required: yes
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this BD. This is required only when creating a new BD.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ dhcp_policy:
+ description:
+ - The DHCP Policy
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Relay Policy
+ type: str
+ required: yes
+ version:
+ description:
+ - The version of DHCP Relay Policy
+ type: int
+ required: yes
+ dhcp_option_policy:
+ description:
+ - The DHCP Option Policy
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the DHCP Option Policy
+ type: str
+ version:
+ description:
+ - The version of the DHCP Option Policy
+ type: int
+ subnets:
+ description:
+ - The subnets associated to this BD.
+ type: list
+ elements: dict
+ suboptions:
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ intersite_bum_traffic:
+ description:
+ - Whether to allow intersite BUM traffic.
+ type: bool
+ optimize_wan_bandwidth:
+ description:
+ - Whether to optimize WAN bandwidth.
+ type: bool
+ layer2_stretch:
+ description:
+ - Whether to enable L2 stretch.
+ type: bool
+ default: true
+ layer2_unknown_unicast:
+ description:
+ - Layer2 unknown unicast.
+ type: str
+ choices: [ flood, proxy ]
+ layer3_multicast:
+ description:
+ - Whether to enable L3 multicast.
+ type: bool
+ unknown_multicast_flooding:
+ description:
+ - Unknown Multicast Flooding can either be Flood or Optimized Flooding
+ type: str
+ choices: [ flood, optimized_flooding ]
+ multi_destination_flooding:
+ description:
+ - Multi-Destination Flooding can either be Flood in BD or just Drop
+ type: str
+ choices: [ flood_in_bd, drop ]
+ ipv6_unknown_multicast_flooding:
+ description:
+ - IPv6 Unknown Multicast Flooding can either be Flood or Optimized Flooding
+ type: str
+ choices: [ flood, optimized_flooding ]
+ arp_flooding:
+ description:
+ - ARP Flooding
+ type: bool
+ virtual_mac_address:
+ description:
+ - Virtual MAC Address
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new BD
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ vrf:
+ name: VRF1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new BD from another Schema
+ mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ vrf:
+ name: VRF1
+ schema: Schema Origin
+ template: Template Origin
+ state: present
+ delegate_to: localhost
+
+- name: Add bd with options available on version 3.1
+ mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ intersite_bum_traffic: true
+ optimize_wan_bandwidth: false
+ layer2_stretch: true
+ layer2_unknown_unicast: flood
+ layer3_multicast: false
+ unknown_multicast_flooding: flood
+ multi_destination_flooding: drop
+ ipv6_unknown_multicast_flooding: flood
+ arp_flooding: false
+ virtual_mac_address: 00:00:5E:00:01:3C
+ subnets:
+ - subnet: 10.0.0.128/24
+ - subnet: 10.0.1.254/24
+ description: 1234567890
+ - ip: 192.168.0.254/24
+ description: "My description for a subnet"
+ scope: private
+ shared: false
+ no_default_gateway: true
+ vrf:
+ name: vrf1
+ schema: Test
+ template: Template1
+ dhcp_policy:
+ name: ansible_test
+ version: 1
+ dhcp_option_policy:
+ name: ansible_test_option
+ version: 1
+
+- name: Remove an BD
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific BDs
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all BDs
+ cisco.mso.mso_schema_template_bd:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec, mso_subnet_spec, mso_dhcp_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bd=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ intersite_bum_traffic=dict(type='bool'),
+ optimize_wan_bandwidth=dict(type='bool'),
+ layer2_stretch=dict(type='bool', default='true'),
+ layer2_unknown_unicast=dict(type='str', choices=['flood', 'proxy']),
+ layer3_multicast=dict(type='bool'),
+ vrf=dict(type='dict', options=mso_reference_spec()),
+ dhcp_policy=dict(type='dict', options=mso_dhcp_spec()),
+ subnets=dict(type='list', elements='dict', options=mso_subnet_spec()),
+ unknown_multicast_flooding=dict(type='str', choices=['optimized_flooding', 'flood']),
+ multi_destination_flooding=dict(type='str', choices=['flood_in_bd', 'drop']),
+ ipv6_unknown_multicast_flooding=dict(type='str', choices=['optimized_flooding', 'flood']),
+ arp_flooding=dict(type='bool'),
+ virtual_mac_address=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['bd']],
+ ['state', 'present', ['bd', 'vrf']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ bd = module.params.get('bd')
+ display_name = module.params.get('display_name')
+ intersite_bum_traffic = module.params.get('intersite_bum_traffic')
+ optimize_wan_bandwidth = module.params.get('optimize_wan_bandwidth')
+ layer2_stretch = module.params.get('layer2_stretch')
+ layer2_unknown_unicast = module.params.get('layer2_unknown_unicast')
+ layer3_multicast = module.params.get('layer3_multicast')
+ vrf = module.params.get('vrf')
+ if vrf is not None and vrf.get('template') is not None:
+ vrf['template'] = vrf.get('template').replace(' ', '')
+ dhcp_policy = module.params.get('dhcp_policy')
+ subnets = module.params.get('subnets')
+ unknown_multicast_flooding = module.params.get('unknown_multicast_flooding')
+ multi_destination_flooding = module.params.get('multi_destination_flooding')
+ ipv6_unknown_multicast_flooding = module.params.get('ipv6_unknown_multicast_flooding')
+ arp_flooding = module.params.get('arp_flooding')
+ virtual_mac_address = module.params.get('virtual_mac_address')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Map choices
+ if unknown_multicast_flooding == 'optimized_flooding':
+ unknown_multicast_flooding = 'opt-flood'
+ if ipv6_unknown_multicast_flooding == 'optimized_flooding':
+ ipv6_unknown_multicast_flooding = 'opt-flood'
+ if multi_destination_flooding == 'flood_in_bd':
+ multi_destination_flooding = 'bd-flood'
+
+ if layer2_unknown_unicast == 'flood':
+ arp_flooding = True
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if schema_obj:
+ schema_id = schema_obj.get('id')
+ else:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get BDs
+ bds = [b.get('name') for b in schema_obj.get('templates')[template_idx]['bds']]
+
+ if bd is not None and bd in bds:
+ bd_idx = bds.index(bd)
+ mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]
+
+ if state == 'query':
+ if bd is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['bds']
+ elif not mso.existing:
+ mso.fail_json(msg="BD '{bd}' not found".format(bd=bd))
+ mso.exit_json()
+
+ bds_path = '/templates/{0}/bds'.format(template)
+ bd_path = '/templates/{0}/bds/{1}'.format(template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=bd_path))
+
+ elif state == 'present':
+ vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
+ subnets = mso.make_subnets(subnets)
+ dhcp_label = mso.make_dhcp_label(dhcp_policy)
+
+ if display_name is None and not mso.existing:
+ display_name = bd
+ if subnets is None and not mso.existing:
+ subnets = []
+
+ payload = dict(
+ name=bd,
+ displayName=display_name,
+ intersiteBumTrafficAllow=intersite_bum_traffic,
+ optimizeWanBandwidth=optimize_wan_bandwidth,
+ l2UnknownUnicast=layer2_unknown_unicast,
+ l2Stretch=layer2_stretch,
+ l3MCast=layer3_multicast,
+ subnets=subnets,
+ vrfRef=vrf_ref,
+ dhcpLabel=dhcp_label,
+ unkMcastAct=unknown_multicast_flooding,
+ multiDstPktAct=multi_destination_flooding,
+ v6unkMcastAct=ipv6_unknown_multicast_flooding,
+ vmac=virtual_mac_address,
+ arpFlood=arp_flooding,
+ )
+
+ mso.sanitize(payload, collate=True, required=['dhcpLabel'])
+ if mso.existing:
+ ops.append(dict(op='replace', path=bd_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=bds_path + '/-', value=mso.sent))
+ mso.existing = mso.proposed
+
+ if 'bdRef' in mso.previous:
+ del mso.previous['bdRef']
+ if 'vrfRef' in mso.previous:
+ mso.previous['vrfRef'] = mso.vrf_dict_from_ref(mso.previous.get('vrfRef'))
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
new file mode 100644
index 00000000..fd95c311
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
@@ -0,0 +1,253 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_bd_subnet
+short_description: Manage BD subnets in schema templates
+description:
+- Manage BD subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ bd:
+ description:
+ - The name of the BD to manage.
+ type: str
+ required: yes
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ aliases: [ ip ]
+ description:
+ description:
+ - The description of this subnet.
+ type: str
+ is_virtual_ip:
+ description:
+ - Treat as Virtual IP Address
+ type: bool
+ default: false
+ scope:
+ description:
+ - The scope of the subnet.
+ type: str
+ default: private
+ choices: [ private, public ]
+ shared:
+ description:
+ - Whether this subnet is shared between VRFs.
+ type: bool
+ no_default_gateway:
+ description:
+ - Whether this subnet has a default gateway.
+ type: bool
+ querier:
+ description:
+ - Whether this subnet is an IGMP querier.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to BD subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new subnet to a BD
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subset from a BD
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific BD subnet
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all BD subnets
+ cisco.mso.mso_schema_template_bd_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ bd: BD 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bd=dict(type='str', required=True),
+ subnet=dict(type='str', aliases=['ip']),
+ description=dict(type='str'),
+ is_virtual_ip=dict(type='bool', default=False),
+ scope=dict(type='str', default='private', choices=['private', 'public']),
+ shared=dict(type='bool', default=False),
+ no_default_gateway=dict(type='bool', default=False),
+ querier=dict(type='bool', default=False),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ bd = module.params.get('bd')
+ subnet = module.params.get('subnet')
+ description = module.params.get('description')
+ is_virtual_ip = module.params.get('is_virtual_ip')
+ scope = module.params.get('scope')
+ shared = module.params.get('shared')
+ no_default_gateway = module.params.get('no_default_gateway')
+ querier = module.params.get('querier')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get BD
+ bds = [b.get('name') for b in schema_obj.get('templates')[template_idx]['bds']]
+ if bd not in bds:
+ mso.fail_json(msg="Provided BD '{0}' does not exist. Existing BDs: {1}".format(bd, ', '.join(bds)))
+ bd_idx = bds.index(bd)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets']]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/templates/{0}/bds/{1}/subnets/{2}'.format(template, bd, subnet_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['bds'][bd_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet IP '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/templates/{0}/bds/{1}/subnets'.format(template, bd)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ if not mso.existing:
+ if description is None:
+ description = subnet
+
+ payload = dict(
+ ip=subnet,
+ description=description,
+ virtual=is_virtual_ip,
+ scope=scope,
+ shared=shared,
+ noDefaultGateway=no_default_gateway,
+ querier=querier,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
new file mode 100644
index 00000000..ab5d0466
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
@@ -0,0 +1,352 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_contract_filter
+short_description: Manage contract filters in schema templates
+description:
+- Manage contract filters in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ contract:
+ description:
+ - The name of the contract to manage.
+ type: str
+ required: yes
+ contract_display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ - This defaults to the contract name when unset on creation.
+ type: str
+ contract_filter_type:
+ description:
+ - The type of filters defined in this contract.
+ - This defaults to C(both-way) when unset on creation.
+ default: both-way
+ type: str
+ choices: [ both-way, one-way ]
+ contract_scope:
+ description:
+ - The scope of the contract.
+ - This defaults to C(vrf) when unset on creation.
+ type: str
+ choices: [ application-profile, global, tenant, vrf ]
+ filter:
+ description:
+ - The filter to associate with this contract.
+ type: str
+ aliases: [ name ]
+ filter_template:
+ description:
+ - The template name in which the filter is located.
+ type: str
+ filter_schema:
+ description:
+ - The schema name in which the filter is located.
+ type: str
+ filter_type:
+ description:
+ - The type of filter to manage.
+ type: str
+ choices: [ both-way, consumer-to-provider, provider-to-consumer ]
+ default: both-way
+ aliases: [ type ]
+ filter_directives:
+ description:
+ - A list of filter directives.
+ type: list
+ elements: str
+ choices: [ log, none, policy_compression ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_filter_entry
+notes:
+- Due to restrictions of the MSO REST API this module creates contracts when needed, and removes them when the last filter has been removed.
+- Due to restrictions of the MSO REST API concurrent modifications to contract filters can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ contract_scope: global
+ filter: Filter 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ filter: Filter 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific contract filter
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ filter: Filter 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all contract filters
+ cisco.mso.mso_schema_template_contract_filter:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ contract: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+FILTER_KEYS = {
+ 'both-way': 'filterRelationships',
+ 'consumer-to-provider': 'filterRelationshipsConsumerToProvider',
+ 'provider-to-consumer': 'filterRelationshipsProviderToConsumer',
+}
+
+
+def main():
+
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ contract=dict(type='str', required=True),
+ contract_display_name=dict(type='str'),
+ contract_scope=dict(type='str', choices=['application-profile', 'global', 'tenant', 'vrf']),
+ contract_filter_type=dict(type='str', default='both-way', choices=['both-way', 'one-way']),
+ filter=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ filter_directives=dict(type='list', elements='str', choices=['log', 'none', 'policy_compression']),
+ filter_template=dict(type='str'),
+ filter_schema=dict(type='str'),
+ filter_type=dict(type='str', default='both-way', choices=list(FILTER_KEYS), aliases=['type']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['filter']],
+ ['state', 'present', ['filter']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ contract = module.params.get('contract')
+ contract_display_name = module.params.get('contract_display_name')
+ contract_filter_type = module.params.get('contract_filter_type')
+ contract_scope = module.params.get('contract_scope')
+ filter_name = module.params.get('filter')
+ filter_directives = module.params.get('filter_directives')
+ filter_template = module.params.get('filter_template')
+ if filter_template is not None:
+ filter_template = filter_template.replace(' ', '')
+ filter_schema = module.params.get('filter_schema')
+ filter_type = module.params.get('filter_type')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ contract_ftype = 'bothWay' if contract_filter_type == 'both-way' else 'oneWay'
+
+ if contract_filter_type == 'both-way' and filter_type != 'both-way':
+ mso.fail_json(msg="You are adding 'one-way' filters to a 'both-way' contract")
+ elif contract_filter_type != 'both-way' and filter_type == 'both-way':
+ mso.fail_json(msg="You are adding 'both-way' filters to a 'one-way' contract")
+ if filter_template is None:
+ filter_template = template
+
+ if filter_schema is None:
+ filter_schema = schema
+
+ filter_key = FILTER_KEYS.get(filter_type)
+
+ # Get schema object
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ filter_schema_id = mso.lookup_schema(filter_schema)
+ # Get contracts
+
+ mso.existing = {}
+ contract_idx = None
+ filter_idx = None
+ contracts = [c.get('name') for c in schema_obj.get('templates')[template_idx]['contracts']]
+
+ if contract in contracts:
+ contract_idx = contracts.index(contract)
+ contract_obj = schema_obj.get('templates')[template_idx]['contracts'][contract_idx]
+
+ filters = [f.get('filterRef') for f in schema_obj.get('templates')[template_idx]['contracts'][contract_idx][filter_key]]
+ filter_ref = mso.filter_ref(schema_id=filter_schema_id, template=filter_template, filter=filter_name)
+ if filter_ref in filters:
+ filter_idx = filters.index(filter_ref)
+ filter_path = '/templates/{0}/contracts/{1}/{2}/{3}'.format(template, contract, filter_key, filter_name)
+ filter = contract_obj.get(filter_key)[filter_idx]
+ mso.existing = mso.update_filter_obj(contract_obj, filter, filter_type)
+
+ if state == 'query':
+ if contract_idx is None:
+ mso.fail_json(msg="Provided contract '{0}' does not exist. Existing contracts: {1}".format(contract, ', '.join(contracts)))
+
+ if filter_name is None:
+ mso.existing = contract_obj.get(filter_key)
+ for filter in mso.existing:
+ filter = mso.update_filter_obj(contract_obj, filter, filter_type)
+
+ elif not mso.existing:
+ mso.fail_json(msg="FilterRef '{filter_ref}' not found".format(filter_ref=filter_ref))
+ mso.exit_json()
+
+ ops = []
+ contract_path = '/templates/{0}/contracts/{1}'.format(template, contract)
+ filters_path = '/templates/{0}/contracts/{1}/{2}'.format(template, contract, filter_key)
+ mso.previous = mso.existing
+
+ if state == 'absent':
+ mso.proposed = mso.sent = {}
+
+ if contract_idx is None:
+ # There was no contract to begin with
+ pass
+ elif filter_idx is None:
+ # There was no filter to begin with
+ pass
+ elif len(filters) == 1:
+ # There is only one filter, remove contract
+ mso.existing = {}
+ ops.append(dict(op='remove', path=contract_path))
+ else:
+ # Remove filter
+ mso.existing = {}
+ ops.append(dict(op='remove', path=filter_path))
+
+ elif state == 'present':
+ if filter_directives is None:
+ filter_directives = ['none']
+
+ if 'policy_compression' in filter_directives:
+ filter_directives.remove('policy_compression')
+ filter_directives.append('no_stats')
+
+ payload = dict(
+ filterRef=dict(
+ filterName=filter_name,
+ templateName=filter_template,
+ schemaId=filter_schema_id,
+ ),
+ directives=filter_directives,
+ )
+
+ mso.sanitize(payload, collate=True, unwanted=['filterType', 'contractScope', 'contractFilterType'])
+ mso.existing = mso.sent
+ if contract_scope is None or contract_scope == 'vrf':
+ contract_scope = 'context'
+ if contract_idx is None:
+ # Contract does not exist, so we have to create it
+ if contract_display_name is None:
+ contract_display_name = contract
+ payload = {
+ 'name': contract,
+ 'displayName': contract_display_name,
+ 'filterType': contract_ftype,
+ 'scope': contract_scope,
+ }
+ ops.append(dict(op='add', path='/templates/{0}/contracts/-'.format(template), value=payload))
+
+ else:
+ # Contract exists, but may require an update
+ if contract_display_name is not None:
+ ops.append(dict(op='replace', path=contract_path + '/displayName', value=contract_display_name))
+ ops.append(dict(op='replace', path=contract_path + '/filterType', value=contract_ftype))
+ ops.append(dict(op='replace', path=contract_path + '/scope', value=contract_scope))
+
+ if contract_display_name:
+ mso.existing['displayName'] = contract_display_name
+ else:
+ mso.existing['displayName'] = contract_obj.get('displayName')
+ mso.existing['filterType'] = filter_type
+ mso.existing['contractScope'] = contract_scope
+ mso.existing['contractFilterType'] = contract_ftype
+
+ if filter_idx is None:
+ # Filter does not exist, so we have to add it
+ ops.append(dict(op='add', path=filters_path + '/-', value=mso.sent))
+
+ else:
+ # Filter exists, we have to update it
+ ops.append(dict(op='replace', path=filter_path, value=mso.sent))
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
new file mode 100644
index 00000000..a05c618f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
@@ -0,0 +1,143 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_deploy
+short_description: Deploy schema templates to sites
+description:
+- Deploy schema templates to sites.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ aliases: [ name ]
+ site:
+ description:
+ - The name of the site B(to undeploy).
+ type: str
+ state:
+ description:
+ - Use C(deploy) to deploy schema template.
+ - Use C(status) to get deployment status.
+ - Use C(undeploy) to deploy schema template from a site.
+ type: str
+ choices: [ deploy, status, undeploy ]
+ default: deploy
+seealso:
+- module: cisco.mso.mso_schema_site
+- module: cisco.mso.mso_schema_template
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Deploy a schema template
+ cisco.mso.mso_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: deploy
+ delegate_to: localhost
+
+- name: Undeploy a schema template
+ cisco.mso.mso_schema_template_deploy:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ site: Site 1
+ state: undeploy
+ delegate_to: localhost
+
+- name: Get deployment status
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: status
+ delegate_to: localhost
+ register: status_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True, aliases=['name']),
+ site=dict(type='str'),
+ state=dict(type='str', default='deploy', choices=['deploy', 'status', 'undeploy']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'undeploy', ['site']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ site = module.params.get('site')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_id = mso.lookup_schema(schema)
+
+ payload = dict(
+ schemaId=schema_id,
+ templateName=template,
+ )
+
+ qs = None
+ if state == 'deploy':
+ path = 'execute/schema/{0}/template/{1}'.format(schema_id, template)
+ elif state == 'status':
+ path = 'status/schema/{0}/template/{1}'.format(schema_id, template)
+ elif state == 'undeploy':
+ path = 'execute/schema/{0}/template/{1}'.format(schema_id, template)
+ site_id = mso.lookup_site(site)
+ qs = dict(undeploy=site_id)
+
+ if not module.check_mode:
+ status = mso.request(path, method='GET', data=payload, qs=qs)
+ mso.exit_json(**status)
+ else:
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
new file mode 100644
index 00000000..1831cb85
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
@@ -0,0 +1,332 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_external_epg
+short_description: Manage external EPGs in schema templates
+description:
+- Manage external EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the external EPG to manage.
+ type: str
+ aliases: [ name, externalepg ]
+ type:
+ description:
+ - The type of external epg.
+ - anp needs to be associated with external epg when the type is cloud.
+ - l3out can be associated with external epg when the type is on-premise.
+ type: str
+ choices: [ on-premise, cloud ]
+ default: on-premise
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ l3out:
+ description:
+ - The L3Out associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the L3Out to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ anp:
+ description:
+ - The anp associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the anp to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ preferred_group:
+ description:
+ - Preferred Group is enabled for this External EPG or not.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new external EPG with external epg in cloud
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ type: cloud
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ external_epg=dict(type='str', aliases=['name', 'externalepg']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ vrf=dict(type='dict', options=mso_reference_spec()),
+ l3out=dict(type='dict', options=mso_reference_spec()),
+ anp=dict(type='dict', options=mso_reference_spec()),
+ preferred_group=dict(type='bool'),
+ type=dict(type='str', default='on-premise', choices=['on-premise', 'cloud']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['external_epg']],
+ ['state', 'present', ['external_epg', 'vrf']],
+ ['type', 'cloud', ['anp']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ external_epg = module.params.get('external_epg')
+ display_name = module.params.get('display_name')
+ vrf = module.params.get('vrf')
+ if vrf is not None and vrf.get('template') is not None:
+ vrf['template'] = vrf.get('template').replace(' ', '')
+ l3out = module.params.get('l3out')
+ if l3out is not None and l3out.get('template') is not None:
+ l3out['template'] = l3out.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ if anp is not None and anp.get('template') is not None:
+ anp['template'] = anp.get('template').replace(' ', '')
+ preferred_group = module.params.get('preferred_group')
+ type_ext_epg = module.params.get('type')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if schema_obj:
+ schema_id = schema_obj.get('id')
+ else:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get external EPGs
+ external_epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
+
+ if external_epg is not None and external_epg in external_epgs:
+ external_epg_idx = external_epgs.index(external_epg)
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][external_epg_idx]
+ if 'externalEpgRef' in mso.existing:
+ del mso.existing['externalEpgRef']
+ if 'vrfRef' in mso.existing:
+ mso.existing['vrfRef'] = mso.dict_from_ref(mso.existing.get('vrfRef'))
+ if 'l3outRef' in mso.existing:
+ mso.existing['l3outRef'] = mso.dict_from_ref(mso.existing.get('l3outRef'))
+ if 'anpRef' in mso.existing:
+ mso.existing['anpRef'] = mso.dict_from_ref(mso.existing.get('anpRef'))
+
+ if state == 'query':
+ if external_epg is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs']
+ elif not mso.existing:
+ mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg))
+ mso.exit_json()
+
+ eepgs_path = '/templates/{0}/externalEpgs'.format(template)
+ eepg_path = '/templates/{0}/externalEpgs/{1}'.format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=eepg_path))
+
+ elif state == 'present':
+ vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
+ l3out_ref = mso.make_reference(l3out, 'l3out', schema_id, template)
+ anp_ref = mso.make_reference(anp, 'anp', schema_id, template)
+ if display_name is None and not mso.existing:
+ display_name = external_epg
+
+ payload = dict(
+ name=external_epg,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ preferredGroup=preferred_group,
+ )
+
+ if type_ext_epg == 'cloud':
+ payload['extEpgType'] = 'cloud'
+ payload['anpRef'] = anp_ref
+ else:
+ payload['l3outRef'] = l3out_ref
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get('contractRelationships'):
+ contract['contractRef'] = mso.dict_from_ref(contract.get('contractRef'))
+ ops.append(dict(op='replace', path=eepg_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=eepgs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
new file mode 100644
index 00000000..9db905bd
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
@@ -0,0 +1,250 @@
+#!/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: mso_schema_template_external_epg_contract
+short_description: Manage Extrnal EPG contracts in schema templates
+description:
+- Manage External EPG contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Devarshi Shah (@devarshishah3)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the EPG to manage.
+ type: str
+ required: yes
+ contract:
+ description:
+ - A contract associated to this EPG.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced BD.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced BD.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+- module: cisco.mso.mso_schema_template_contract_filter
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a contract to an EPG
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_external_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ external_epg=dict(type='str', required=True),
+ contract=dict(type='dict', options=mso_contractref_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['contract']],
+ ['state', 'present', ['contract']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ external_epg = module.params.get('external_epg')
+ contract = module.params.get('contract')
+ if contract is not None and contract.get('template') is not None:
+ contract['template'] = contract.get('template').replace(' ', '')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ if contract:
+ if contract.get('schema') is None:
+ contract['schema'] = schema
+ contract['schema_id'] = mso.lookup_schema(contract.get('schema'))
+ if contract.get('template') is None:
+ contract['template'] = template
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get EPG
+ epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
+ if external_epg not in epgs:
+ mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=', '.join(epgs)))
+ epg_idx = epgs.index(external_epg)
+
+ # Get Contract
+ if contract:
+ contracts = [(c.get('contractRef'),
+ c.get('relationshipType')) for c in schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['contractRelationships']]
+ contract_ref = mso.contract_ref(**contract)
+ if (contract_ref, contract.get('type')) in contracts:
+ contract_idx = contracts.index((contract_ref, contract.get('type')))
+ contract_path = '/templates/{0}/externalEpgs/{1}/contractRelationships/{2}'.format(template, external_epg, contract_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['contractRelationships'][contract_idx]
+
+ if state == 'query':
+ if not contract:
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['contractRelationships']
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
+
+ if 'contractRef' in mso.existing:
+ mso.existing['contractRef'] = mso.dict_from_ref(mso.existing.get('contractRef'))
+ mso.exit_json()
+
+ contracts_path = '/templates/{0}/externalEpgs/{1}/contractRelationships'.format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=contract_path))
+
+ elif state == 'present':
+ payload = dict(
+ relationshipType=contract.get('type'),
+ contractRef=dict(
+ contractName=contract.get('name'),
+ templateName=contract.get('template'),
+ schemaId=contract.get('schema_id'),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=contracts_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if 'contractRef' in mso.previous:
+ mso.previous['contractRef'] = mso.dict_from_ref(mso.previous.get('contractRef'))
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
new file mode 100644
index 00000000..0ed2cc3d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
@@ -0,0 +1,249 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_external_epg_selector
+short_description: Manage External EPG selector in schema templates
+description:
+- Manage External EPG selector in schema templates on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+- Cindy Zhao (@cizhao)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the External EPG to be managed.
+ type: str
+ required: yes
+ selector:
+ description:
+ - The name of the selector.
+ type: str
+ expressions:
+ description:
+ - Expressions associated to this selector.
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The name of the expression which in this case is always IP address.
+ required: true
+ type: str
+ choices: [ ip_address ]
+ operator:
+ description:
+ - The operator associated with the expression which in this case is always equals.
+ required: true
+ type: str
+ choices: [ equals ]
+ value:
+ description:
+ - The value of the IP Address / Subnet associated with the expression.
+ required: true
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_external_epg
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a selector to an External EPG
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ expressions:
+ - type: ip_address
+ operator: equals
+ value: 10.0.0.0
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Selector
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Selector
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ selector: selector_1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Selectors
+ cisco.mso.mso_schema_template_external_epg_selector:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: extEPG 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_expression_spec_ext_epg
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ external_epg=dict(type='str', required=True),
+ selector=dict(type='str'),
+ expressions=dict(type='list', elements='dict', options=mso_expression_spec_ext_epg()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['selector']],
+ ['state', 'present', ['selector']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ external_epg = module.params.get('external_epg')
+ selector = module.params.get('selector')
+ expressions = module.params.get('expressions')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get External EPG
+ external_epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
+ if external_epg not in external_epgs:
+ mso.fail_json(msg="Provided external epg '{external_epg}' does not exist. Existing epgs: {external_epgs}"
+ .format(external_epg=external_epg, external_epgs=', '.join(external_epgs)))
+ external_epg_idx = external_epgs.index(external_epg)
+
+ # Get Selector
+ selectors = [s.get('name') for s in schema_obj.get('templates')[template_idx]['externalEpgs'][external_epg_idx]['selectors']]
+ if selector in selectors:
+ selector_idx = selectors.index(selector)
+ selector_path = '/templates/{0}/externalEpgs/{1}/selectors/{2}'.format(template, external_epg, selector_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][external_epg_idx]['selectors'][selector_idx]
+
+ if state == 'query':
+ if selector is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][external_epg_idx]['selectors']
+ elif not mso.existing:
+ mso.fail_json(msg="Selector '{selector}' not found".format(selector=selector))
+ mso.exit_json()
+
+ selectors_path = '/templates/{0}/externalEpgs/{1}/selectors/-'.format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=selector_path))
+
+ elif state == 'present':
+ # Get expressions
+ types = dict(ip_address='ipAddress')
+ all_expressions = []
+ if expressions:
+ for expression in expressions:
+ type_val = expression.get('type')
+ operator = expression.get('operator')
+ value = expression.get('value')
+ all_expressions.append(dict(
+ key=types.get(type_val),
+ operator=operator,
+ value=value,
+ ))
+
+ payload = dict(
+ name=selector,
+ expressions=all_expressions,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=selector_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=selectors_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.existing != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
new file mode 100644
index 00000000..17829011
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
@@ -0,0 +1,220 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_external_epg_subnet
+short_description: Manage External EPG subnets in schema templates
+description:
+- Manage External EPG subnets in schema templates on Cisco ACI Multi-Site.
+author:
+- Devarshi Shah (@devarshishah3)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the External EPG to manage.
+ type: str
+ required: yes
+ subnet:
+ description:
+ - The IP range in CIDR notation.
+ type: str
+ required: true
+ scope:
+ description:
+ - The scope of the subnet.
+ type: list
+ elements: str
+ aggregate:
+ description:
+ - The aggregate option for the subnet.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- Due to restrictions of the MSO REST API concurrent modifications to EPG subnets can be dangerous and corrupt data.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new subnet to an External EPG
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: present
+ delegate_to: localhost
+
+- name: Remove a subnet from an External EPG
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific External EPG subnet
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: EPG 1
+ subnet: 10.0.0.0/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all External EPGs subnets
+ cisco.mso.mso_schema_template_external_epg_subnet:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ external_epg=dict(type='str', required=True),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ subnet=dict(type='str', required=True),
+ scope=dict(type='list', elements='str', default=[]),
+ aggregate=dict(type='list', elements='str', default=[]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['subnet']],
+ ['state', 'present', ['subnet']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ external_epg = module.params.get('external_epg')
+ subnet = module.params.get('subnet')
+ scope = module.params.get('scope')
+ aggregate = module.params.get('aggregate')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get EPG
+ external_epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
+ if external_epg not in external_epgs:
+ mso.fail_json(msg="Provided External EPG '{epg}' does not exist. Existing epgs: {epgs}".format(epg=external_epg, epgs=', '.join(external_epgs)))
+ epg_idx = external_epgs.index(external_epg)
+
+ # Get Subnet
+ subnets = [s.get('ip') for s in schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['subnets']]
+ if subnet in subnets:
+ subnet_idx = subnets.index(subnet)
+ # FIXME: Changes based on index are DANGEROUS
+ subnet_path = '/templates/{0}/externalEpgs/{1}/subnets/{2}'.format(template, external_epg, subnet_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['subnets'][subnet_idx]
+
+ if state == 'query':
+ if subnet is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][epg_idx]['subnets']
+ elif not mso.existing:
+ mso.fail_json(msg="Subnet '{subnet}' not found".format(subnet=subnet))
+ mso.exit_json()
+
+ subnets_path = '/templates/{0}/externalEpgs/{1}/subnets'.format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.existing = {}
+ ops.append(dict(op='remove', path=subnet_path))
+
+ elif state == 'present':
+ payload = dict(
+ ip=subnet,
+ scope=scope,
+ aggregate=aggregate,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=subnet_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=subnets_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
new file mode 100644
index 00000000..1831cb85
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
@@ -0,0 +1,332 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_external_epg
+short_description: Manage external EPGs in schema templates
+description:
+- Manage external EPGs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ external_epg:
+ description:
+ - The name of the external EPG to manage.
+ type: str
+ aliases: [ name, externalepg ]
+ type:
+ description:
+ - The type of external epg.
+ - anp needs to be associated with external epg when the type is cloud.
+ - l3out can be associated with external epg when the type is on-premise.
+ type: str
+ choices: [ on-premise, cloud ]
+ default: on-premise
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ l3out:
+ description:
+ - The L3Out associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the L3Out to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ anp:
+ description:
+ - The anp associated with the external epg.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the anp to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced anp.
+ - If this parameter is unspecified, it defaults to the current template.
+ type: str
+ preferred_group:
+ description:
+ - Preferred Group is enabled for this External EPG or not.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Add a new external EPG with external epg in cloud
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: External EPG 1
+ type: cloud
+ vrf:
+ name: VRF
+ schema: Schema 1
+ template: Template 1
+ anp:
+ name: ANP1
+ schema: Schema 1
+ template: Template 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an external EPG
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ external_epg: external EPG1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all external EPGs
+ cisco.mso.mso_schema_template_external_epg:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ external_epg=dict(type='str', aliases=['name', 'externalepg']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ vrf=dict(type='dict', options=mso_reference_spec()),
+ l3out=dict(type='dict', options=mso_reference_spec()),
+ anp=dict(type='dict', options=mso_reference_spec()),
+ preferred_group=dict(type='bool'),
+ type=dict(type='str', default='on-premise', choices=['on-premise', 'cloud']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['external_epg']],
+ ['state', 'present', ['external_epg', 'vrf']],
+ ['type', 'cloud', ['anp']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ external_epg = module.params.get('external_epg')
+ display_name = module.params.get('display_name')
+ vrf = module.params.get('vrf')
+ if vrf is not None and vrf.get('template') is not None:
+ vrf['template'] = vrf.get('template').replace(' ', '')
+ l3out = module.params.get('l3out')
+ if l3out is not None and l3out.get('template') is not None:
+ l3out['template'] = l3out.get('template').replace(' ', '')
+ anp = module.params.get('anp')
+ if anp is not None and anp.get('template') is not None:
+ anp['template'] = anp.get('template').replace(' ', '')
+ preferred_group = module.params.get('preferred_group')
+ type_ext_epg = module.params.get('type')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if schema_obj:
+ schema_id = schema_obj.get('id')
+ else:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get external EPGs
+ external_epgs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['externalEpgs']]
+
+ if external_epg is not None and external_epg in external_epgs:
+ external_epg_idx = external_epgs.index(external_epg)
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs'][external_epg_idx]
+ if 'externalEpgRef' in mso.existing:
+ del mso.existing['externalEpgRef']
+ if 'vrfRef' in mso.existing:
+ mso.existing['vrfRef'] = mso.dict_from_ref(mso.existing.get('vrfRef'))
+ if 'l3outRef' in mso.existing:
+ mso.existing['l3outRef'] = mso.dict_from_ref(mso.existing.get('l3outRef'))
+ if 'anpRef' in mso.existing:
+ mso.existing['anpRef'] = mso.dict_from_ref(mso.existing.get('anpRef'))
+
+ if state == 'query':
+ if external_epg is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['externalEpgs']
+ elif not mso.existing:
+ mso.fail_json(msg="External EPG '{external_epg}' not found".format(external_epg=external_epg))
+ mso.exit_json()
+
+ eepgs_path = '/templates/{0}/externalEpgs'.format(template)
+ eepg_path = '/templates/{0}/externalEpgs/{1}'.format(template, external_epg)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=eepg_path))
+
+ elif state == 'present':
+ vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
+ l3out_ref = mso.make_reference(l3out, 'l3out', schema_id, template)
+ anp_ref = mso.make_reference(anp, 'anp', schema_id, template)
+ if display_name is None and not mso.existing:
+ display_name = external_epg
+
+ payload = dict(
+ name=external_epg,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ preferredGroup=preferred_group,
+ )
+
+ if type_ext_epg == 'cloud':
+ payload['extEpgType'] = 'cloud'
+ payload['anpRef'] = anp_ref
+ else:
+ payload['l3outRef'] = l3out_ref
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get('contractRelationships'):
+ contract['contractRef'] = mso.dict_from_ref(contract.get('contractRef'))
+ ops.append(dict(op='replace', path=eepg_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=eepgs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
new file mode 100644
index 00000000..3a85ce09
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
@@ -0,0 +1,363 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_filter_entry
+short_description: Manage filter entries in schema templates
+description:
+- Manage filter entries in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ filter:
+ description:
+ - The name of the filter to manage.
+ type: str
+ required: yes
+ filter_display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ entry:
+ description:
+ - The filter entry name to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ aliases: [ entry_display_name ]
+ description:
+ description:
+ - The description of this filer entry.
+ type: str
+ aliases: [ entry_description ]
+ ethertype:
+ description:
+ - The ethernet type to use for this filter entry.
+ type: str
+ choices: [ arp, fcoe, ip, ipv4, ipv6, mac-security, mpls-unicast, trill, unspecified ]
+ ip_protocol:
+ description:
+ - The IP protocol to use for this filter entry.
+ type: str
+ choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ]
+ tcp_session_rules:
+ description:
+ - A list of TCP session rules.
+ type: list
+ elements: str
+ choices: [ acknowledgement, established, finish, synchronize, reset, unspecified ]
+ source_from:
+ description:
+ - The source port range from.
+ type: str
+ source_to:
+ description:
+ - The source port range to.
+ type: str
+ destination_from:
+ description:
+ - The destination port range from.
+ type: str
+ destination_to:
+ description:
+ - The destination port range to.
+ type: str
+ arp_flag:
+ description:
+ - The ARP flag to use for this filter entry.
+ type: str
+ choices: [ reply, request, unspecified ]
+ stateful:
+ description:
+ - Whether this filter entry is stateful.
+ type: bool
+ default: no
+ fragments_only:
+ description:
+ - Whether this filter entry only matches fragments.
+ type: bool
+ default: no
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_contract_filter
+notes:
+- Due to restrictions of the MSO REST API this module creates filters when needed, and removes them when the last entry has been removed.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific filter entry
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ filter: Filter 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all filter entries
+ cisco.mso.mso_schema_template_filter_entry:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ filter=dict(type='str', required=True),
+ filter_display_name=dict(type='str'),
+ entry=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ description=dict(type='str', aliases=['entry_description']),
+ display_name=dict(type='str', aliases=['entry_display_name']),
+ ethertype=dict(type='str', choices=['arp', 'fcoe', 'ip', 'ipv4', 'ipv6', 'mac-security', 'mpls-unicast', 'trill', 'unspecified']),
+ ip_protocol=dict(type='str', choices=['eigrp', 'egp', 'icmp', 'icmpv6', 'igmp', 'igp', 'l2tp', 'ospfigp', 'pim', 'tcp', 'udp', 'unspecified']),
+ tcp_session_rules=dict(type='list', elements='str', choices=['acknowledgement', 'established', 'finish', 'synchronize', 'reset', 'unspecified']),
+ source_from=dict(type='str'),
+ source_to=dict(type='str'),
+ destination_from=dict(type='str'),
+ destination_to=dict(type='str'),
+ arp_flag=dict(type='str', choices=['reply', 'request', 'unspecified']),
+ stateful=dict(type='bool'),
+ fragments_only=dict(type='bool'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['entry']],
+ ['state', 'present', ['entry']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ filter_name = module.params.get('filter')
+ filter_display_name = module.params.get('filter_display_name')
+ entry = module.params.get('entry')
+ display_name = module.params.get('display_name')
+ description = module.params.get('description')
+ ethertype = module.params.get('ethertype')
+ ip_protocol = module.params.get('ip_protocol')
+ tcp_session_rules = module.params.get('tcp_session_rules')
+ source_from = module.params.get('source_from')
+ source_to = module.params.get('source_to')
+ destination_from = module.params.get('destination_from')
+ destination_to = module.params.get('destination_to')
+ arp_flag = module.params.get('arp_flag')
+ stateful = module.params.get('stateful')
+ fragments_only = module.params.get('fragments_only')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{template}' does not exist. Existing templates: {templates}".format(template=template,
+ templates=', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get filters
+ mso.existing = {}
+ filter_idx = None
+ entry_idx = None
+ filters = [f.get('name') for f in schema_obj.get('templates')[template_idx]['filters']]
+ if filter_name in filters:
+ filter_idx = filters.index(filter_name)
+
+ entries = [f.get('name') for f in schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries']]
+ if entry in entries:
+ entry_idx = entries.index(entry)
+ mso.existing = schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries'][entry_idx]
+
+ if state == 'query':
+ if entry is None:
+ if filter_idx is None:
+ mso.fail_json(msg="Filter '{filter}' not found".format(filter=filter_name))
+ mso.existing = schema_obj.get('templates')[template_idx]['filters'][filter_idx]['entries']
+ elif not mso.existing:
+ mso.fail_json(msg="Entry '{entry}' not found".format(entry=entry))
+ mso.exit_json()
+
+ filters_path = '/templates/{0}/filters'.format(template)
+ filter_path = '/templates/{0}/filters/{1}'.format(template, filter_name)
+ entries_path = '/templates/{0}/filters/{1}/entries'.format(template, filter_name)
+ entry_path = '/templates/{0}/filters/{1}/entries/{2}'.format(template, filter_name, entry)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ mso.proposed = mso.sent = {}
+
+ if filter_idx is None:
+ # There was no filter to begin with
+ pass
+ elif entry_idx is None:
+ # There was no entry to begin with
+ pass
+ elif len(entries) == 1:
+ # There is only one entry, remove filter
+ mso.existing = {}
+ ops.append(dict(op='remove', path=filter_path))
+
+ else:
+ mso.existing = {}
+ ops.append(dict(op='remove', path=entry_path))
+
+ elif state == 'present':
+
+ if not mso.existing:
+ if display_name is None:
+ display_name = entry
+ if description is None:
+ description = ''
+ if ethertype is None:
+ ethertype = 'unspecified'
+ if ip_protocol is None:
+ ip_protocol = 'unspecified'
+ if tcp_session_rules is None:
+ tcp_session_rules = ['unspecified']
+ if source_from is None:
+ source_from = 'unspecified'
+ if source_to is None:
+ source_to = 'unspecified'
+ if destination_from is None:
+ destination_from = 'unspecified'
+ if destination_to is None:
+ destination_to = 'unspecified'
+ if arp_flag is None:
+ arp_flag = 'unspecified'
+ if stateful is None:
+ stateful = False
+ if fragments_only is None:
+ fragments_only = False
+
+ payload = dict(
+ name=entry,
+ displayName=display_name,
+ description=description,
+ etherType=ethertype,
+ ipProtocol=ip_protocol,
+ tcpSessionRules=tcp_session_rules,
+ sourceFrom=source_from,
+ sourceTo=source_to,
+ destinationFrom=destination_from,
+ destinationTo=destination_to,
+ arpFlag=arp_flag,
+ stateful=stateful,
+ matchOnlyFragments=fragments_only,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if filter_idx is None:
+ # Filter does not exist, so we have to create it
+ if filter_display_name is None:
+ filter_display_name = filter_name
+
+ payload = dict(
+ name=filter_name,
+ displayName=filter_display_name,
+ entries=[mso.sent],
+ )
+
+ ops.append(dict(op='add', path=filters_path + '/-', value=payload))
+
+ elif entry_idx is None:
+ # Entry does not exist, so we have to add it
+ ops.append(dict(op='add', path=entries_path + '/-', value=mso.sent))
+
+ else:
+ # Entry exists, we have to update it
+ for (key, value) in mso.sent.items():
+ ops.append(dict(op='replace', path=entry_path + '/' + key, value=value))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
new file mode 100644
index 00000000..5aa4e557
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_l3out
+short_description: Manage l3outs in schema templates
+description:
+- Manage l3outs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ l3out:
+ description:
+ - The name of the l3out to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ vrf:
+ description:
+ - The VRF associated to this L3out.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the VRF to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced VRF.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new L3out
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ vrf:
+ name: vrfName
+ schema: vrfSchema
+ template: vrfTemplate
+ state: present
+ delegate_to: localhost
+
+- name: Remove an L3out
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific L3outs
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ l3out: L3out 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all L3outs
+ cisco.mso.mso_schema_template_l3out:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_reference_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ l3out=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ vrf=dict(type='dict', options=mso_reference_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['l3out']],
+ ['state', 'present', ['l3out', 'vrf']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ l3out = module.params.get('l3out')
+ display_name = module.params.get('display_name')
+ vrf = module.params.get('vrf')
+ if vrf is not None and vrf.get('template') is not None:
+ vrf['template'] = vrf.get('template').replace(' ', '')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if schema_obj:
+ schema_id = schema_obj.get('id')
+ else:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get L3out
+ l3outs = [l3.get('name') for l3 in schema_obj.get('templates')[template_idx]['intersiteL3outs']]
+
+ if l3out is not None and l3out in l3outs:
+ l3out_idx = l3outs.index(l3out)
+ mso.existing = schema_obj.get('templates')[template_idx]['intersiteL3outs'][l3out_idx]
+
+ if state == 'query':
+ if l3out is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['intersiteL3outs']
+ elif not mso.existing:
+ mso.fail_json(msg="L3out '{l3out}' not found".format(l3out=l3out))
+ mso.exit_json()
+
+ l3outs_path = '/templates/{0}/intersiteL3outs'.format(template)
+ l3out_path = '/templates/{0}/intersiteL3outs/{1}'.format(template, l3out)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=l3out_path))
+
+ elif state == 'present':
+ vrf_ref = mso.make_reference(vrf, 'vrf', schema_id, template)
+
+ if display_name is None and not mso.existing:
+ display_name = l3out
+
+ payload = dict(
+ name=l3out,
+ displayName=display_name,
+ vrfRef=vrf_ref,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=l3out_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=l3outs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
new file mode 100644
index 00000000..c31fbecf
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_migrate
+short_description: Migrate Bridge Domains (BDs) and EPGs between templates
+description:
+- Migrate BDs and EPGs between templates of same and different schemas.
+author:
+- Anvitha Jain (@anvitha-jain)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ bds:
+ description:
+ - The name of the BDs to migrate.
+ type: list
+ elements: str
+ epgs:
+ description:
+ - The name of the EPGs and the ANP it is in to migrate.
+ type: list
+ elements: dict
+ suboptions:
+ epg:
+ description:
+ - The name of the EPG to migrate.
+ type: str
+ required: yes
+ anp:
+ description:
+ - The name of the anp to migrate.
+ type: str
+ required: yes
+ target_schema:
+ description:
+ - The name of the target_schema.
+ type: str
+ required: yes
+ target_template:
+ description:
+ - The name of the target_template.
+ type: str
+ required: yes
+ state:
+ description:
+ - Use C(present) for adding.
+ type: str
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Migration of objects between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 1
+ target_template: Template 2
+ bds:
+ - BD
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ state: present
+ delegate_to: localhost
+
+- name: Migration of objects between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ bds:
+ - BD
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ state: present
+ delegate_to: localhost
+
+- name: Migration of BD object between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 1
+ target_template: Template 2
+ bds:
+ - BD
+ - BD1
+ state: present
+ delegate_to: localhost
+
+- name: Migration of BD object between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ bds:
+ - BD
+ - BD1
+ state: present
+ delegate_to: localhost
+
+- name: Migration of EPG objects between templates of same schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ - epg: EPG2
+ anp: ANP2
+ state: present
+ delegate_to: localhost
+
+- name: Migration of EPG objects between templates of different schema
+ mso_schema_template_migrate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ target_schema: Schema 2
+ target_template: Template 2
+ epgs:
+ - epg: EPG1
+ anp: ANP
+ - epg: EPG2
+ anp: ANP2
+ state: present
+ delegate_to: localhost
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_object_migrate_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ bds=dict(type='list', elements='str'),
+ epgs=dict(type='list', elements='dict', options=mso_object_migrate_spec()),
+ target_schema=dict(type='str', required=True),
+ target_template=dict(type='str', required=True),
+ state=dict(type='str', default='present'),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ target_schema = module.params.get('target_schema')
+ target_template = module.params.get('target_template').replace(' ', '')
+ bds = module.params.get('bds')
+ epgs = module.params.get('epgs')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ schema_id = mso.get_obj('schemas', displayName=schema).get('id')
+
+ target_schema_id = mso.get_obj('schemas', displayName=target_schema).get('id')
+
+ if state == 'present':
+ if schema_id is not None:
+ bds_payload = []
+ if bds is not None:
+ for bd in bds:
+ bds_payload.append(dict(name=bd))
+
+ anp_dict = {}
+ if epgs is not None:
+ for epg in epgs:
+ if epg.get('anp') in anp_dict:
+ anp_dict[epg.get('anp')].append(dict(name=epg.get('epg')))
+ else:
+ anp_dict[epg.get('anp')] = [dict(name=epg.get('epg'))]
+
+ anps_payload = []
+ for anp, epgs_payload in anp_dict.items():
+ anps_payload.append(dict(name=anp, epgs=epgs_payload))
+
+ payload = dict(
+ targetSchemaId=target_schema_id,
+ targetTemplateName=target_template,
+ bds=bds_payload,
+ anps=anps_payload,
+ )
+
+ template = template.replace(' ', '%20')
+
+ target_template = target_template.replace(' ', '%20') # removes API error for extra space
+
+ mso.existing = mso.request(path='/api/v1/migrate/schema/{0}/template/{1}'.format(schema_id, template), method='POST', data=payload)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
new file mode 100644
index 00000000..65a918e9
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_schema_template_vrf
+short_description: Manage VRFs in schema templates
+description:
+- Manage VRFs in schema templates on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF to manage.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name as displayed on the MSO web interface.
+ type: str
+ layer3_multicast:
+ description:
+ - Whether to enable L3 multicast.
+ type: bool
+ vzany:
+ description:
+ - Whether to enable vzAny.
+ type: bool
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new VRF
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ state: present
+ delegate_to: localhost
+
+- name: Remove an VRF
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific VRFs
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all VRFs
+ cisco.mso.mso_schema_template_vrf:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', aliases=['name']), # This parameter is not required for querying all objects
+ display_name=dict(type='str'),
+ layer3_multicast=dict(type='bool'),
+ vzany=dict(type='bool'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['vrf']],
+ ['state', 'present', ['vrf']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ display_name = module.params.get('display_name')
+ layer3_multicast = module.params.get('layer3_multicast')
+ vzany = module.params.get('vzany')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get ANP
+ vrfs = [v.get('name') for v in schema_obj.get('templates')[template_idx]['vrfs']]
+
+ if vrf is not None and vrf in vrfs:
+ vrf_idx = vrfs.index(vrf)
+ mso.existing = schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]
+
+ if state == 'query':
+ if vrf is None:
+ mso.existing = schema_obj.get('templates')[template_idx]['vrfs']
+ elif not mso.existing:
+ mso.fail_json(msg="VRF '{vrf}' not found".format(vrf=vrf))
+ mso.exit_json()
+
+ vrfs_path = '/templates/{0}/vrfs'.format(template)
+ vrf_path = '/templates/{0}/vrfs/{1}'.format(template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=vrf_path))
+
+ elif state == 'present':
+ if display_name is None and not mso.existing:
+ display_name = vrf
+
+ payload = dict(
+ name=vrf,
+ displayName=display_name,
+ l3MCast=layer3_multicast,
+ vzAnyEnabled=vzany
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ # clean contractRef to fix api issue
+ for contract in mso.sent.get('vzAnyConsumerContracts'):
+ contract['contractRef'] = mso.dict_from_ref(contract.get('contractRef'))
+ for contract in mso.sent.get('vzAnyProviderContracts'):
+ contract['contractRef'] = mso.dict_from_ref(contract.get('contractRef'))
+ ops.append(dict(op='replace', path=vrf_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=vrfs_path + '/-', value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
new file mode 100644
index 00000000..22f96f0d
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
@@ -0,0 +1,264 @@
+#!/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: mso_schema_template_vrf_contract
+short_description: Manage vrf contracts in schema templates
+description:
+- Manage vrf contracts in schema templates on Cisco ACI Multi-Site.
+author:
+- Cindy Zhao (@cizhao)
+version_added: '0.0.8'
+options:
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: yes
+ template:
+ description:
+ - The name of the template to change.
+ type: str
+ required: yes
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ required: yes
+ contract:
+ description:
+ - A contract associated to this VRF.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract to associate with.
+ required: true
+ type: str
+ schema:
+ description:
+ - The schema that defines the referenced contract.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The template that defines the referenced contract.
+ type: str
+ type:
+ description:
+ - The type of contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_vrf
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a contract to a VRF
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: absent
+ delegate_to: localhost
+
+- name: Query a specific Contract
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ contract:
+ name: Contract 1
+ type: consumer
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Contracts
+ cisco.mso.mso_schema_template_vrf_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ vrf: VRF 1
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ schema=dict(type='str', required=True),
+ template=dict(type='str', required=True),
+ vrf=dict(type='str', required=True),
+ contract=dict(type='dict', options=mso_contractref_spec()),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['contract']],
+ ['state', 'present', ['contract']],
+ ],
+ )
+
+ schema = module.params.get('schema')
+ template = module.params.get('template').replace(' ', '')
+ vrf = module.params.get('vrf')
+ contract = module.params.get('contract')
+ if contract is not None and contract.get('template') is not None:
+ contract['template'] = contract.get('template').replace(' ', '')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+ if contract:
+ if contract.get('schema') is None:
+ contract['schema'] = schema
+ contract['schema_id'] = mso.lookup_schema(contract.get('schema'))
+ if contract.get('template') is None:
+ contract['template'] = template
+
+ # Get schema_id
+ schema_obj = mso.get_obj('schemas', displayName=schema)
+ if not schema_obj:
+ mso.fail_json(msg="Provided schema '{0}' does not exist".format(schema))
+
+ schema_path = 'schemas/{id}'.format(**schema_obj)
+
+ # Get template
+ templates = [t.get('name') for t in schema_obj.get('templates')]
+ if template not in templates:
+ mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ', '.join(templates)))
+ template_idx = templates.index(template)
+
+ # Get VRF
+ vrfs = [e.get('name') for e in schema_obj.get('templates')[template_idx]['vrfs']]
+ if vrf not in vrfs:
+ mso.fail_json(msg="Provided vrf '{vrf}' does not exist. Existing vrfs: {vrfs}".format(vrf=vrf, vrfs=', '.join(vrfs)))
+ vrf_idx = vrfs.index(vrf)
+ vrf_obj = schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]
+
+ if not vrf_obj.get('vzAnyEnabled'):
+ mso.fail_json(msg="vzAny attribute on vrf '{0}' is disabled.".format(vrf))
+
+ # Get Contract
+ if contract:
+ provider_contracts = [c.get('contractRef') for c in schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyProviderContracts']]
+ consumer_contracts = [c.get('contractRef') for c in schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyConsumerContracts']]
+ contract_ref = mso.contract_ref(**contract)
+ if contract_ref in provider_contracts and contract.get('type') == 'provider':
+ contract_idx = provider_contracts.index(contract_ref)
+ contract_path = '/templates/{0}/vrfs/{1}/vzAnyProviderContracts/{2}'.format(template, vrf, contract_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyProviderContracts'][contract_idx]
+ if contract_ref in consumer_contracts and contract.get('type') == 'consumer':
+ contract_idx = consumer_contracts.index(contract_ref)
+ contract_path = '/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/{2}'.format(template, vrf, contract_idx)
+ mso.existing = schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyConsumerContracts'][contract_idx]
+ if mso.existing.get('contractRef'):
+ mso.existing['contractRef'] = mso.dict_from_ref(mso.existing.get('contractRef'))
+ mso.existing['relationshipType'] = contract.get('type')
+
+ if state == 'query':
+ if not contract:
+ provider_contracts = [dict(contractRef=mso.dict_from_ref(c.get('contractRef')),
+ relationshipType='provider') for c in schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyProviderContracts']]
+ consumer_contracts = [dict(contractRef=mso.dict_from_ref(c.get('contractRef')),
+ relationshipType='consumer') for c in schema_obj.get('templates')[template_idx]['vrfs'][vrf_idx]['vzAnyConsumerContracts']]
+ mso.existing = provider_contracts + consumer_contracts
+ elif not mso.existing:
+ mso.fail_json(msg="Contract '{0}' not found".format(contract.get('name')))
+
+ mso.exit_json()
+
+ if contract.get('type') == 'provider':
+ contracts_path = '/templates/{0}/vrfs/{1}/vzAnyProviderContracts/-'.format(template, vrf)
+ if contract.get('type') == 'consumer':
+ contracts_path = '/templates/{0}/vrfs/{1}/vzAnyConsumerContracts/-'.format(template, vrf)
+ ops = []
+
+ mso.previous = mso.existing
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=contract_path))
+
+ elif state == 'present':
+ payload = dict(
+ contractRef=dict(
+ contractName=contract.get('name'),
+ templateName=contract.get('template'),
+ schemaId=contract.get('schema_id'),
+ ),
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=contract_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=contracts_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+ mso.existing['relationshipType'] = contract.get('type')
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(schema_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_site.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_site.py
new file mode 100644
index 00000000..2b6cf82f
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_site.py
@@ -0,0 +1,252 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_site
+short_description: Manage sites
+description:
+- Manage sites on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ apic_password:
+ description:
+ - The password for the APICs.
+ type: str
+ apic_site_id:
+ description:
+ - The site ID of the APICs.
+ type: str
+ apic_username:
+ description:
+ - The username for the APICs.
+ type: str
+ default: admin
+ apic_login_domain:
+ description:
+ - The AAA login domain for the username for the APICs.
+ type: str
+ site:
+ description:
+ - The name of the site.
+ type: str
+ aliases: [ name ]
+ labels:
+ description:
+ - The labels for this site.
+ - Labels that do not already exist will be automatically created.
+ type: list
+ elements: str
+ location:
+ description:
+ - Location of the site.
+ type: dict
+ suboptions:
+ latitude:
+ description:
+ - The latitude of the location of the site.
+ type: float
+ longitude:
+ description:
+ - The longitude of the location of the site.
+ type: float
+ urls:
+ description:
+ - A list of URLs to reference the APICs.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ description: North European Datacenter
+ apic_username: mso_admin
+ apic_password: AnotherSecretPassword
+ apic_site_id: 12
+ urls:
+ - 10.2.3.4
+ - 10.2.4.5
+ - 10.3.5.6
+ labels:
+ - NEDC
+ - Europe
+ - Diegem
+ location:
+ latitude: 50.887318
+ longitude: 4.447084
+ state: present
+ delegate_to: localhost
+
+- name: Remove a site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ state: absent
+ delegate_to: localhost
+
+- name: Query a site
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ site: north_europe
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all sites
+ cisco.mso.mso_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ location_arg_spec = dict(
+ latitude=dict(type='float'),
+ longitude=dict(type='float'),
+ )
+
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ apic_password=dict(type='str', no_log=True),
+ apic_site_id=dict(type='str'),
+ apic_username=dict(type='str', default='admin'),
+ apic_login_domain=dict(type='str'),
+ labels=dict(type='list', elements='str'),
+ location=dict(type='dict', options=location_arg_spec),
+ site=dict(type='str', aliases=['name']),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ urls=dict(type='list', elements='str'),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['site']],
+ ['state', 'present', ['apic_site_id', 'site']],
+ ],
+ )
+
+ apic_username = module.params.get('apic_username')
+ apic_password = module.params.get('apic_password')
+ apic_site_id = module.params.get('apic_site_id')
+ site = module.params.get('site')
+ location = module.params.get('location')
+ if location is not None:
+ latitude = module.params.get('location')['latitude']
+ longitude = module.params.get('location')['longitude']
+ state = module.params.get('state')
+ urls = module.params.get('urls')
+ apic_login_domain = module.params.get('apic_login_domain')
+
+ mso = MSOModule(module)
+
+ site_id = None
+ path = 'sites'
+
+ # Convert labels
+ labels = mso.lookup_labels(module.params.get('labels'), 'site')
+
+ # Query for mso.existing object(s)
+ if site:
+ mso.existing = mso.get_obj(path, name=site)
+ if mso.existing:
+ site_id = mso.existing.get('id')
+ # If we found an existing object, continue with it
+ path = 'sites/{id}'.format(id=site_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE', qs=dict(force='true'))
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ apicSiteId=apic_site_id,
+ id=site_id,
+ name=site,
+ urls=urls,
+ labels=labels,
+ username=apic_username,
+ password=apic_password,
+ )
+
+ if location is not None:
+ payload['location'] = dict(
+ lat=latitude,
+ long=longitude,
+ )
+
+ if apic_login_domain is not None and apic_login_domain not in ['', 'local', 'Local']:
+ payload['username'] = 'apic#{0}\\{1}'.format(apic_login_domain, apic_username)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='PUT', data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='POST', data=mso.sent)
+
+ if 'password' in mso.existing:
+ mso.existing['password'] = '******'
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
new file mode 100644
index 00000000..68f54290
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_tenant
+short_description: Manage tenants
+description:
+- Manage tenants on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ aliases: [ name ]
+ display_name:
+ description:
+ - The name of the tenant to be displayed in the web UI.
+ type: str
+ description:
+ description:
+ - The description for this tenant.
+ type: str
+ users:
+ description:
+ - A list of associated users for this tenant.
+ - Using this property will replace any existing associated users.
+ - Admin user is always added to the associated user list irrespective of this parameter being used.
+ type: list
+ elements: str
+ sites:
+ description:
+ - A list of associated sites for this tenant.
+ - Using this property will replace any existing associated sites.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Add a new tenant
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ display_name: North European Datacenter
+ description: This tenant manages the NEDC environment.
+ state: present
+ delegate_to: localhost
+
+- name: Remove a tenant
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ state: absent
+ delegate_to: localhost
+
+- name: Query a tenant
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: north_europe
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all tenants
+ cisco.mso.mso_tenant:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ description=dict(type='str'),
+ display_name=dict(type='str'),
+ tenant=dict(type='str', aliases=['name']),
+ users=dict(type='list', elements='str'),
+ sites=dict(type='list', elements='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['tenant']],
+ ['state', 'present', ['tenant']],
+ ],
+ )
+
+ description = module.params.get('description')
+ display_name = module.params.get('display_name')
+ tenant = module.params.get('tenant')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ # Convert sites and users
+ sites = mso.lookup_sites(module.params.get('sites'))
+ users = mso.lookup_users(module.params.get('users'))
+
+ tenant_id = None
+ path = 'tenants'
+
+ # Query for existing object(s)
+ if tenant:
+ mso.existing = mso.get_obj(path, name=tenant)
+ if mso.existing:
+ tenant_id = mso.existing.get('id')
+ # If we found an existing object, continue with it
+ path = 'tenants/{id}'.format(id=tenant_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE')
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ description=description,
+ id=tenant_id,
+ name=tenant,
+ displayName=display_name,
+ siteAssociations=sites,
+ userAssociations=users,
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ # Ensure displayName is not undefined
+ if mso.sent.get('displayName') is None:
+ mso.sent['displayName'] = tenant
+
+ if mso.existing:
+ if mso.check_changed():
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='PUT', data=mso.sent)
+ else:
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='POST', data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
new file mode 100644
index 00000000..bb65b3fe
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
@@ -0,0 +1,391 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_tenant_site
+short_description: Manage tenants with cloud sites.
+description:
+- Manage tenants with cloud sites on Cisco ACI Multi-Site.
+author:
+- Shreyas Srish (@shrsr)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ required: yes
+ site:
+ description:
+ - The name of the site.
+ - This can either be cloud site or non-cloud site.
+ type: str
+ aliases: [ name ]
+ cloud_account:
+ description:
+ - Required for cloud site.
+ - Account id of AWS in the form '000000000000'.
+ - Account id of Azure in the form 'uni/tn-(tenant_name)/act-[(subscription_id)]-azure_vendor-azure'.
+ - Example values inside account id of Azure '(tenant_name)=tenant_test and (subscription_id)=10'.
+ type: str
+ security_domains:
+ description:
+ - List of security domains for cloud sites.
+ type: list
+ elements: str
+ default: []
+ aws_account_org:
+ description:
+ - AWS account for organization.
+ default: false
+ type: bool
+ aws_trusted:
+ description:
+ - AWS account's access in trusted mode. Credentials are required, when set to false.
+ type: bool
+ aws_access_key:
+ description:
+ - AWS account's access key id. This is required when aws_trusted is set to false.
+ type: str
+ azure_access_type:
+ description:
+ - Managed mode for Azure.
+ - Unmanaged mode for Azure.
+ - Shared mode if the attribute is not specified.
+ choices: [ managed, unmanaged, shared ]
+ default: shared
+ type: str
+ azure_active_directory_id:
+ description:
+ - Azure account's active directory id.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ azure_active_directory_name:
+ description:
+ - Azure account's active directory name. Example being 'CiscoINSBUAd' as active directory name.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ azure_subscription_id:
+ description:
+ - Azure account's subscription id.
+ - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
+ type: str
+ azure_application_id:
+ description:
+ - Azure account's application id.
+ - This attribute is required when azure_access_type is either in managed mode or unmanaged mode.
+ type: str
+ azure_credential_name:
+ description:
+ - Azure account's credential name.
+ - This attribute is required when azure_access_type is in unmanaged mode.
+ type: str
+ secret_key:
+ description:
+ - secret key of AWS for untrusted account. Required when aws_trusted is set to false.
+ - secret key of Azure account for unmanaged identity. Required in unmanaged mode of Azure account.
+ type: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Associate a non-cloud site with a tenant
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: present
+ delegate_to: localhost
+
+- name: Associate AWS site with a tenant, with aws_trusted set to true
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: '000000000000'
+ aws_trusted: true
+ state: present
+ delegate_to: localhost
+
+- name: Associate AWS site with a tenant, with aws_trusted set to false
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: AWS
+ cloud_account: '000000000000'
+ aws_trusted: false
+ aws_access_key: '1'
+ secret_key: '0'
+ aws_account_org: false
+ state: present
+ delegate_to: localhost
+
+- name: Associate Azure site in managed mode
+ mso.cisco.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
+ azure_access_type: managed
+ azure_subscription_id: '9'
+ azure_application_id: '100'
+ state: present
+ delegate_to: localhost
+
+- name: Associate Azure site in unmanaged mode
+ mso.cisco.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ cloud_account: uni/tn-ansible_test/act-[9]-azure_vendor-azure
+ azure_access_type: unmanaged
+ azure_subscription_id: '9'
+ azure_application_id: '100'
+ azure_credential_name: cApicApp
+ secret_key: iins
+ azure_active_directory_id: '32'
+ azure_active_directory_name: CiscoINSBUAd
+ state: present
+ delegate_to: localhost
+
+- name: Dissociate a site
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: absent
+ delegate_to: localhost
+
+- name: Query a site
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ site: site_name
+ state: query
+ delegate_to: localhost
+
+- name: Query all sites of a tenant
+ cisco.mso.mso_tenant_site:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: tenant_name
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type='str', aliases=['name'], required=True),
+ site=dict(type='str', aliases=['name']),
+ cloud_account=dict(type='str'),
+ security_domains=dict(type='list', elements='str', default=[]),
+ aws_trusted=dict(type='bool'),
+ azure_access_type=dict(type='str', default='shared', choices=['managed', 'unmanaged', 'shared']),
+ azure_active_directory_id=dict(type='str'),
+ aws_access_key=dict(type='str'),
+ aws_account_org=dict(type='bool', default='false'),
+ azure_active_directory_name=dict(type='str'),
+ azure_subscription_id=dict(type='str'),
+ azure_application_id=dict(type='str'),
+ azure_credential_name=dict(type='str'),
+ secret_key=dict(type='str'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['tenant', 'site']],
+ ['state', 'present', ['tenant', 'site']],
+ ],
+ )
+
+ state = module.params.get('state')
+ security_domains = module.params.get('security_domains')
+ cloud_account = module.params.get('cloud_account')
+ azure_access_type = module.params.get('azure_access_type')
+ azure_credential_name = module.params.get('azure_credential_name')
+ azure_application_id = module.params.get('azure_application_id')
+ azure_active_directory_id = module.params.get('azure_active_directory_id')
+ azure_active_directory_name = module.params.get('azure_active_directory_name')
+ azure_subscription_id = module.params.get('azure_subscription_id')
+ secret_key = module.params.get('secret_key')
+ aws_account_org = module.params.get('aws_account_org')
+ aws_access_key = module.params.get('aws_access_key')
+ aws_trusted = module.params.get('aws_trusted')
+
+ mso = MSOModule(module)
+
+ # Get tenant_id and site_id
+ tenant_id = mso.lookup_tenant(module.params.get('tenant'))
+ site_id = mso.lookup_site(module.params.get('site'))
+ tenants = [(t.get('id')) for t in mso.query_objs('tenants')]
+ tenant_idx = tenants.index((tenant_id))
+
+ # set tenent and port paths
+ tenant_path = 'tenants/{0}'.format(tenant_id)
+ ops = []
+ ports_path = '/siteAssociations/-'
+ port_path = '/siteAssociations/{0}'.format(site_id)
+
+ payload = dict(
+ siteId=site_id,
+ securityDomains=security_domains,
+ cloudAccount=cloud_account,
+ )
+
+ if cloud_account:
+ if 'azure' in cloud_account:
+ azure_account = dict(
+ accessType=azure_access_type,
+ securityDomains=security_domains,
+ vendor='azure',
+ )
+
+ payload['azureAccount'] = [azure_account]
+
+ cloudSubscription = dict(
+ cloudSubscriptionId=azure_subscription_id,
+ cloudApplicationId=azure_application_id,
+ )
+
+ payload['azureAccount'][0]['cloudSubscription'] = cloudSubscription
+
+ if azure_access_type == 'shared':
+ payload['azureAccount'] = []
+
+ if azure_access_type == 'managed':
+ if not azure_subscription_id:
+ mso.fail_json(msg="azure_susbscription_id is required when in managed mode.")
+ if not azure_application_id:
+ mso.fail_json(msg="azure_application_id is required when in managed mode.")
+ payload['azureAccount'][0]['cloudApplication'] = []
+ payload['azureAccount'][0]['cloudActiveDirectory'] = []
+
+ if azure_access_type == 'unmanaged':
+ if not azure_subscription_id:
+ mso.fail_json(msg="azure_subscription_id is required when in unmanaged mode.")
+ if not azure_application_id:
+ mso.fail_json(msg="azure_application_id is required when in unmanaged mode.")
+ if not secret_key:
+ mso.fail_json(msg="secret_key is required when in unmanaged mode.")
+ if not azure_active_directory_id:
+ mso.fail_json(msg="azure_active_directory_id is required when in unmanaged mode.")
+ if not azure_active_directory_name:
+ mso.fail_json(msg="azure_active_directory_name is required when in unmanaged mode.")
+ if not azure_credential_name:
+ mso.fail_json(msg="azure_credential_name is required when in unmanaged mode.")
+ azure_account.update(
+ accessType='credentials',
+ )
+ cloudApplication = dict(
+ cloudApplicationId=azure_application_id,
+ cloudCredentialName=azure_credential_name,
+ secretKey=secret_key,
+ cloudActiveDirectoryId=azure_active_directory_id
+ )
+ cloudActiveDirectory = dict(
+ cloudActiveDirectoryId=azure_active_directory_id,
+ cloudActiveDirectoryName=azure_active_directory_name
+ )
+ payload['azureAccount'][0]['cloudApplication'] = [cloudApplication]
+ payload['azureAccount'][0]['cloudActiveDirectory'] = [cloudActiveDirectory]
+
+ else:
+ aws_account = dict(
+ accountId=cloud_account,
+ isTrusted=aws_trusted,
+ accessKeyId=aws_access_key,
+ secretKey=secret_key,
+ isAccountInOrg=aws_account_org,
+ )
+
+ if not aws_trusted:
+ if not aws_access_key:
+ mso.fail_json(msg="aws_access_key is a required field in untrusted mode.")
+ if not secret_key:
+ mso.fail_json(msg="secret_key is a required field in untrusted mode.")
+ payload['awsAccount'] = [aws_account]
+
+ sites = [(s.get('siteId')) for s in mso.query_objs('tenants')[tenant_idx]['siteAssociations']]
+
+ if site_id in sites:
+ site_idx = sites.index((site_id))
+ mso.existing = mso.query_objs('tenants')[tenant_idx]['siteAssociations'][site_idx]
+
+ if state == 'query':
+ if len(sites) == 0:
+ mso.fail_json(msg="No site associated with tenant Id {0}".format(tenant_id))
+ elif site_id not in sites and site_id is not None:
+ mso.fail_json(msg="Site Id {0} not associated with tenant Id {1}".format(site_id, tenant_id))
+ elif site_id is None:
+ mso.existing = mso.query_objs('tenants')[tenant_idx]['siteAssociations']
+ mso.exit_json()
+
+ mso.previous = mso.existing
+
+ if state == 'absent':
+ if mso.existing:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op='remove', path=port_path))
+ if state == 'present':
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op='replace', path=port_path, value=mso.sent))
+ else:
+ ops.append(dict(op='add', path=ports_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(tenant_path, method='PATCH', data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_user.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_user.py
new file mode 100644
index 00000000..560063f8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_user.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_user
+short_description: Manage users
+description:
+- Manage users on Cisco ACI Multi-Site.
+author:
+- Dag Wieers (@dagwieers)
+options:
+ user:
+ description:
+ - The name of the user.
+ type: str
+ aliases: [ name ]
+ user_password:
+ description:
+ - The password of the user.
+ type: str
+ first_name:
+ description:
+ - The first name of the user.
+ - This parameter is required when creating new users.
+ type: str
+ last_name:
+ description:
+ - The last name of the user.
+ - This parameter is required when creating new users.
+ type: str
+ email:
+ description:
+ - The email address of the user.
+ - This parameter is required when creating new users.
+ type: str
+ phone:
+ description:
+ - The phone number of the user.
+ - This parameter is required when creating new users.
+ type: str
+ account_status:
+ description:
+ - The status of the user account.
+ type: str
+ choices: [ active, inactive ]
+ domain:
+ description:
+ - The domain this user belongs to.
+ - When creating new users, this defaults to C(Local).
+ type: str
+ roles:
+ description:
+ - The roles for this user and their access types (read or write).
+ - Access type defaults to C(write).
+ type: list
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+notes:
+- A default installation of ACI Multi-Site ships with admin password 'we1come!' which requires a password change on first login.
+ See the examples of how to change the 'admin' password using Ansible.
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Update initial admin password
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: initialPassword
+ validate_certs: false
+ user: admin
+ user_password: newPassword
+ state: present
+ delegate_to: localhost
+
+- name: Add a new user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ user_password: userPassword
+ first_name: Dag
+ last_name: Wieers
+ email: dag@wieers.com
+ phone: +32 478 436 299
+ roles:
+ - name: siteManager
+ access_type: write
+ - name: schemaManager
+ access_type: read
+ state: present
+ delegate_to: localhost
+
+- name: Add a new user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ first_name: Dag
+ last_name: Wieers
+ email: dag@wieers.com
+ phone: +32 478 436 299
+ roles:
+ - powerUser
+ delegate_to: localhost
+
+- name: Remove a user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ state: absent
+ delegate_to: localhost
+
+- name: Query a user
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ user: dag
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all users
+ cisco.mso.mso_user:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ validate_certs: false
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r''' # '''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, issubset
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ user=dict(type='str', aliases=['name']),
+ user_password=dict(type='str', no_log=True),
+ first_name=dict(type='str'),
+ last_name=dict(type='str'),
+ email=dict(type='str'),
+ phone=dict(type='str'),
+ # TODO: What possible options do we have ?
+ account_status=dict(type='str', choices=['active', 'inactive']),
+ domain=dict(type='str'),
+ roles=dict(type='list'),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'query']),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ['state', 'absent', ['user']],
+ ['state', 'present', ['user']],
+ ],
+ )
+
+ user_name = module.params.get('user')
+ user_password = module.params.get('user_password')
+ first_name = module.params.get('first_name')
+ last_name = module.params.get('last_name')
+ email = module.params.get('email')
+ phone = module.params.get('phone')
+ account_status = module.params.get('account_status')
+ state = module.params.get('state')
+
+ mso = MSOModule(module)
+
+ roles = mso.lookup_roles(module.params.get('roles'))
+ domain = mso.lookup_domain(module.params.get('domain'))
+
+ user_id = None
+ path = 'users'
+
+ # Query for existing object(s)
+ if user_name:
+ mso.existing = mso.get_obj(path, username=user_name)
+ if mso.existing:
+ user_id = mso.existing.get('id')
+ # If we found an existing object, continue with it
+ path = 'users/{id}'.format(id=user_id)
+ else:
+ mso.existing = mso.query_objs(path)
+
+ if state == 'query':
+ pass
+
+ elif state == 'absent':
+ mso.previous = mso.existing
+ if mso.existing:
+ if module.check_mode:
+ mso.existing = {}
+ else:
+ mso.existing = mso.request(path, method='DELETE')
+
+ elif state == 'present':
+ mso.previous = mso.existing
+
+ payload = dict(
+ id=user_id,
+ username=user_name,
+ firstName=first_name,
+ lastName=last_name,
+ emailAddress=email,
+ phoneNumber=phone,
+ accountStatus=account_status,
+ domainId=domain,
+ roles=roles,
+ # active=True,
+ # remote=True,
+ )
+
+ if user_password is not None:
+ payload.update(password=user_password)
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.sent.get('accountStatus') is None:
+ mso.sent['accountStatus'] = 'active'
+
+ if mso.existing:
+ if not issubset(mso.sent, mso.existing):
+ # NOTE: Since MSO always returns '******' as password, we need to assume a change
+ if 'password' in mso.proposed:
+ mso.module.warn("A password change is assumed, as the MSO REST API does not return passwords we do not know.")
+ mso.result['changed'] = True
+
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='PUT', data=mso.sent)
+
+ else:
+ if user_password is None:
+ mso.fail_json("The user {0} does not exist. The 'user_password' attribute is required to create a new user.".format(user_name))
+ if module.check_mode:
+ mso.existing = mso.proposed
+ else:
+ mso.existing = mso.request(path, method='POST', data=mso.sent)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_version.py b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_version.py
new file mode 100644
index 00000000..33259826
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/cisco/mso/plugins/modules/mso_version.py
@@ -0,0 +1,71 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE 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: mso_version
+short_description: Get version of MSO
+description:
+- Retrieve the code version of Cisco Multi-Site Orchestrator.
+author:
+- Lionel Hercot (@lhercot)
+options:
+ state:
+ description:
+ - Use C(query) for retrieving the version object.
+ type: str
+ choices: [ query ]
+ default: query
+extends_documentation_fragment: cisco.mso.modules
+'''
+
+EXAMPLES = r'''
+- name: Get MSO version
+ cisco.mso.mso_version:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+'''
+
+RETURN = r'''
+'''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ state=dict(type='str', default='query', choices=['query'])
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ mso = MSOModule(module)
+
+ path = 'platform/version'
+
+ # Query for mso.existing object
+ mso.existing = mso.query_obj(path)
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()