From 0434868b5da4a4926d611186e226aa4c1eb61127 Mon Sep 17 00:00:00 2001 From: Dag Wieers Date: Thu, 24 Jan 2019 14:40:11 +0100 Subject: [PATCH] Required changes for schema-modifying modules (#51270) A new set of schema modifying modules need some additional infrastructure. --- lib/ansible/module_utils/network/aci/mso.py | 130 ++++++++++++++++++-- 1 file changed, 118 insertions(+), 12 deletions(-) diff --git a/lib/ansible/module_utils/network/aci/mso.py b/lib/ansible/module_utils/network/aci/mso.py index 46c4b9c95a..9ede7dfcca 100644 --- a/lib/ansible/module_utils/network/aci/mso.py +++ b/lib/ansible/module_utils/network/aci/mso.py @@ -87,6 +87,24 @@ def mso_argument_spec(): ) +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( + ip=dict(type='str', required=True), + description=dict(type='str'), + scope=dict(type='str'), + shared=dict(type='bool'), + no_default_gateway=dict(type='bool'), + ) + + class MSOModule(object): def __init__(self, module): @@ -104,6 +122,7 @@ class MSOModule(object): self.sent = dict() # debug output + self.has_modified = False self.filter_string = '' self.method = None self.path = None @@ -177,11 +196,19 @@ class MSOModule(object): self.response = info['msg'] self.status = info['status'] + # self.result['info'] = info + + # Get change status from HTTP headers + if 'modified' in info: + self.has_modified = True + if info['modified'] == 'false': + self.result['changed'] = False + elif info['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 self.method in ('DELETE', 'PATCH', 'POST', 'PUT') and self.status in (200, 201, 204): -# self.result['changed'] = True if output: return json.loads(output) @@ -194,9 +221,13 @@ class MSOModule(object): # 500: Internal Server Error, 501: Not Implemented elif self.status >= 400: try: - payload = json.loads(resp.read()) - except Exception: - payload = json.loads(info['body']) + output = resp.read() + payload = json.loads(output) + except (ValueError, AttributeError): + try: + payload = json.loads(info['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: @@ -209,9 +240,15 @@ class MSOModule(object): 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[key]: for kw_key, kw_value in kwargs.items(): if kw_value is None: @@ -273,6 +310,18 @@ class MSOModule(object): ids.append(dict(siteId=s['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) + if 'id' not in t: + self.module.fail_json(msg="Tenant lookup failed for '%s': %s" % (tenant, t)) + return t['id'] + def lookup_users(self, users): ''' Look up users and return their ids ''' if users is None: @@ -307,17 +356,72 @@ class MSOModule(object): ids.append(l['id']) return ids - def sanitize(self, updates, collate=False, required_keys=None): + 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['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['id'] + + if data.get('template') is not None: + template = data['template'] + + refname = '%sName' % reftype + + return { + refname: data['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: + subnets.append(dict( + ip=subnet['ip'], + description=subnet.get('description', subnet['ip']), + scope=subnet.get('scope', 'private'), + shared=subnet.get('shared', False), + noDefaultGateway=subnet.get('no_default_gateway', False), + )) + + return subnets + + def sanitize(self, updates, collate=False, required=None, unwanted=None): ''' Clean up unset keys from a request payload ''' - if required_keys is None: - required_keys = [] + 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_keys: + if key in required: pass # Remove unspecified values @@ -344,7 +448,8 @@ class MSOModule(object): if self.params['state'] in ('absent', 'present'): if self.params['output_level'] in ('debug', 'info'): self.result['previous'] = self.previous - if self.previous != self.existing: + # FIXME: Modified header only works for PATCH + if not self.has_modified and self.previous != self.existing: self.result['changed'] = True # Return the gory details when we need it @@ -360,7 +465,7 @@ class MSOModule(object): self.result['current'] = self.existing - if self.module._diff: + if self.module._diff and self.result['changed'] is True: self.result['diff'] = dict( before=self.previous, after=self.existing, @@ -375,7 +480,8 @@ class MSOModule(object): if self.params['state'] in ('absent', 'present'): if self.params['output_level'] in ('debug', 'info'): self.result['previous'] = self.previous - if self.previous != self.existing: + # FIXME: Modified header only works for PATCH + if not self.has_modified and self.previous != self.existing: self.result['changed'] = True # Return the gory details when we need it