diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/cisco/mso/plugins | |
parent | Initial commit. (diff) | |
download | ansible-upstream.tar.xz ansible-upstream.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/cisco/mso/plugins')
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() |