Files
offline_kubespray/collection/cisco/mso/plugins/module_utils/mso.py
ByeonJungHun 360c6eef4a offline 작업
2024-02-19 16:02:29 +09:00

1132 lines
43 KiB
Python

# -*- 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 ast
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, to_text
from ansible.module_utils.connection import Connection
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=False, aliases=['hostname'], fallback=(env_fallback, ['MSO_HOST'])),
port=dict(type='int', required=False, fallback=(env_fallback, ['MSO_PORT'])),
username=dict(type='str', required=False, fallback=(env_fallback, ['MSO_USERNAME', 'ANSIBLE_NET_USERNAME'])),
password=dict(type='str', required=False, 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', fallback=(env_fallback, ['MSO_USE_PROXY'])),
use_ssl=dict(type='bool', fallback=(env_fallback, ['MSO_USE_SSL'])),
validate_certs=dict(type='bool', 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_epg_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),
)
def mso_subnet_spec():
subnet_spec = mso_epg_subnet_spec()
subnet_spec.update(dict(querier=dict(type='bool', default=False)))
return subnet_spec
def mso_bd_subnet_spec():
subnet_spec = mso_epg_subnet_spec()
subnet_spec.update(dict(querier=dict(type='bool', default=False)))
subnet_spec.update(dict(primary=dict(type='bool', default=False)))
subnet_spec.update(dict(virtual=dict(type='bool', default=False)))
return subnet_spec
def mso_dhcp_spec():
return dict(
dhcp_option_policy=dict(type='dict', options=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'}
self.platform = "mso"
# 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
if self.module._debug:
self.module.warn('Enable debug output because ANSIBLE_DEBUG was set.')
self.params['output_level'] = 'debug'
if self.module._socket_path is None:
if self.params.get('use_ssl') is None:
self.params['use_ssl'] = True
if self.params.get('use_proxy') is None:
self.params['use_proxy'] = True
if self.params.get('validate_certs') is None:
self.params['validate_certs'] = True
# 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.base_only_uri = '{protocol}://{host}:{port}/'.format(**self.params)
self.baseuri = '{0}api/v1/'.format(self.base_only_uri)
else:
self.base_only_uri = '{protocol}://{host}/'.format(**self.params)
self.baseuri = '{0}api/v1/'.format(self.base_only_uri)
if self.params.get('host') is None:
self.fail_json(msg="Parameter 'host' is required when not using the HTTP API connection plugin")
if self.params.get('password'):
# Perform password-based authentication, log on using password
self.login()
else:
self.fail_json(msg="Parameter 'password' is required for authentication")
else:
self.connection = Connection(self.module._socket_path)
if self.connection.get_platform() == "cisco.nd":
self.platform = "nd"
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.fail_json(msg="Login domain '%s' is not a valid domain name." % domain)
if 'id' not in d:
self.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', 'admin'), 'password': self.params.get('password'), 'domainId': domain_id}
else:
payload = {'username': self.params.get('username', 'admin'), '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') not in [200, 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.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, api_version="v1"):
''' 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 {}
# if method in ['PATCH', 'PUT']:
# if qs is not None:
# qs['enableVersionCheck'] = 'true'
# else:
# qs = dict(enableVersionCheck='true')
if method in ['PATCH']:
if qs is not None:
qs['validate'] = 'false'
else:
qs = dict(validate='false')
resp = None
if self.module._socket_path:
self.connection.set_params(self.params)
if api_version is not None:
uri = '/mso/api/{0}/{1}'.format(api_version, self.path)
else:
uri = self.path
if qs is not None:
uri = uri + update_qs(qs)
try:
info = self.connection.send_request(method, uri, json.dumps(data))
self.url = info.get('url')
info.pop('date')
except Exception as e:
try:
error_obj = json.loads(to_text(e))
except Exception:
error_obj = dict(error=dict(
code=-1,
message="Unable to parse error output as JSON. Raw error message: {0}".format(e),
exception=to_text(e)
))
pass
self.fail_json(msg=error_obj['error']['message'])
else:
if api_version is not None:
self.url = '{0}api/{1}/{2}'.format(self.base_only_uri, api_version, self.path.lstrip('/'))
else:
self.url = '{0}{1}'.format(self.base_only_uri, self.path.lstrip('/'))
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', -1)
# 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
if self.status in (200, 201, 202):
try:
output = resp.read()
if output:
try:
return json.loads(output)
except Exception as e:
self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. {0}".format(e))
self.result['raw'] = output
return
except AttributeError:
return info.get('body')
# 204: No Content
elif self.status == 204:
return {}
# 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:
self.result['status'] = self.status
body = info.get('body')
if body is not None:
try:
if isinstance(body, dict):
payload = body
else:
payload = json.loads(body)
except Exception as e:
self.error = dict(code=-1, message="Unable to parse output as JSON, see 'raw' output. %s" % e)
self.result['raw'] = body
self.fail_json(msg='MSO Error:', data=data, info=info)
self.error = payload
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)
else:
# Connection error
msg = 'Connection failed for {0}. {1}'.format(info.get('url'), info.get('msg'))
self.error = msg
self.fail_json(msg=msg)
return {}
def query_objs(self, path, key=None, api_version='v1', **kwargs):
''' Query the MSO REST API for objects in a path '''
found = []
objs = self.request(path, api_version=api_version, method='GET')
if objs == {} or objs == []:
return found
if key is None:
key = path
if isinstance(objs, dict):
if key not in objs:
self.fail_json(msg="Key '{0}' missing from data".format(key), data=objs)
objs_list = objs.get(key)
else:
objs_list = objs
for obj in objs_list:
for kw_key, kw_value in kwargs.items():
if kw_value is None:
continue
if isinstance(kw_value, dict):
obj_value = obj.get(kw_key)
if obj_value is not None and isinstance(obj_value, dict):
breakout = False
for kw_key_lvl2, kw_value_lvl2 in kw_value.items():
if obj_value.get(kw_key_lvl2) != kw_value_lvl2:
breakout = True
break
if breakout:
break
else:
break
elif obj.get(kw_key) != kw_value:
break
else:
found.append(obj)
return found
def query_obj(self, path, api_version='v1', **kwargs):
''' Query the MSO REST API for the whole object at a path '''
obj = self.request(path, api_version=api_version, method='GET')
if obj == {}:
return {}
for kw_key, kw_value in kwargs.items():
if kw_value is None:
continue
if isinstance(kw_value, dict):
obj_value = obj.get(kw_key)
if obj_value is not None and isinstance(obj_value, dict):
for kw_key_lvl2, kw_value_lvl2 in kw_value.items():
if obj_value.get(kw_key_lvl2) != kw_value_lvl2:
return {}
elif obj.get(kw_key) != kw_value:
return {}
return obj
def get_obj(self, path, api_version='v1', **kwargs):
''' Get a specific object from a set of MSO REST objects '''
objs = self.query_objs(path, api_version=api_version, **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
schema_summary = self.query_objs('schemas/list-identity', key='schemas', displayName=schema)
if not schema_summary:
self.fail_json(msg="Provided schema '{0}' does not exist.".format(schema))
schema_id = schema_summary[0].get('id')
if not schema_id:
self.fail_json(msg="Schema lookup failed for schema '{0}': '{1}'".format(schema, schema_id))
return schema_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.fail_json(msg="Domain '%s' is not a valid domain name." % domain)
if 'id' not in d:
self.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:
access_type = "readWrite"
try:
role = ast.literal_eval(role)
if type(role) is dict and 'name' in role:
name = role.get('name')
if role.get('access_type') == 'read':
access_type = 'readOnly'
except ValueError:
name = role
r = self.get_obj('roles', name=name)
if not r:
self.fail_json(msg="Role '%s' is not a valid role name." % name)
if 'id' not in r:
self.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.fail_json(msg="Site '%s' is not a valid site name." % site)
if 'id' not in s:
self.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.fail_json(msg="Site '%s' is not a valid site name." % site)
if 'id' not in s:
self.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.fail_json(msg="Tenant '%s' is not valid tenant name." % tenant)
if 'id' not in t:
self.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.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:
users = ['admin']
elif 'admin' not in users:
users.append('admin')
ids = []
for user in users:
if self.platform == "nd":
u = self.get_obj('users', loginID=user, api_version='v2')
else:
u = self.get_obj('users', username=user)
if not u:
self.fail_json(msg="User '%s' is not a valid user name." % user)
if 'id' not in u:
if 'userID' not in u:
self.fail_json(msg="User lookup failed for user '%s': %s" % (user, u))
id = dict(userId=u.get('userID'))
else:
id = dict(userId=u.get('id'))
if id in ids:
self.fail_json(msg="User '%s' is duplicate." % user)
ids.append(id)
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.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 l3out_ref(self, **data):
''' Create l3outRef string '''
return '/schemas/{schema_id}/templates/{template}/l3outs/{l3out}'.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][1]: schema_id,
uri_map[category][2]: template_name,
}
self.recursive_dict_from_ref_regex(name, result, uri_map[category][0])
return result
else:
self.fail_json(msg="There was no group in search: {data}".format(data=data))
def recursive_dict_from_ref_regex(self, data, result, category):
continued_ref_regex = re.compile(r'(.*?)\/([a-zA-Z]+.*)')
section_ref_regex = re.compile(r'([a-zA-Z]+)\/(.*)')
dic_name = continued_ref_regex.search(data)
if dic_name is not None:
result[category] = dic_name.group(1)
next_section = dic_name.group(2)
dic_next_section = section_ref_regex.search(next_section)
if dic_next_section is not None:
next_name = dic_next_section.group(2)
self.recursive_dict_from_ref_regex(next_name, result, dic_next_section.group(1).rstrip("s") + "Name")
else:
result[category] = data
def recursive_dict_from_ref(self, data):
for key in data:
if key.endswith('Ref'):
data[key] = self.dict_from_ref(data.get(key))
if isinstance(data[key], list):
for item in data[key]:
self.recursive_dict_from_ref(item)
return 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, is_bd_subnet=True):
''' 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')
subnet_payload = dict(
ip=subnet.get('ip'),
description=str(subnet.get('description')),
scope=subnet.get('scope'),
shared=subnet.get('shared'),
noDefaultGateway=subnet.get('no_default_gateway'),
)
if is_bd_subnet:
subnet_payload.update(dict(querier=subnet.get('querier'), primary=subnet.get('primary'), virtual=subnet.get('virtual')))
subnets.append(subnet_payload)
return subnets
def make_dhcp_label(self, data):
''' Create a DHCP policy from input '''
if data is None:
return None
if type(data) == list:
dhcps = []
for dhcp in data:
if 'dhcp_option_policy' in dhcp:
dhcp['dhcpOptionLabel'] = dhcp.get('dhcp_option_policy')
del dhcp['dhcp_option_policy']
dhcps.append(dhcp)
return dhcps
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
if isinstance(updates, dict):
# 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)
elif updates is not None:
self.sent = updates
# Update self.proposed
self.proposed = 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', 'clone'):
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
self.result['socket'] = self.module._socket_path
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
self.result['socket'] = self.module._socket_path
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
def query_schema(self, schema):
schema_id = self.lookup_schema(schema)
schema_path = "schemas/{0}".format(schema_id)
schema_obj = self.query_obj(schema_path, displayName=schema)
if not schema_obj:
self.module.fail_json(msg="Schema '{0}' is not a valid schema name.".format(schema))
return schema_id, schema_path, schema_obj