38from copy
import deepcopy
46 from OpenSSL.crypto
import FILETYPE_PEM, load_privatekey, sign
56 HAS_LXML_ETREE =
False
60 from xmljson
import cobra
61 HAS_XMLJSON_COBRA =
True
63 HAS_XMLJSON_COBRA =
False
68 host=dict(type=
'str', required=
True, aliases=[
'hostname']),
69 port=dict(type=
'int', required=
False),
70 username=dict(type=
'str', default=
'admin', aliases=[
'user']),
71 password=dict(type=
'str', no_log=
True),
72 private_key=dict(type=
'str', aliases=[
'cert_key'], no_log=
True),
73 certificate_name=dict(type=
'str', aliases=[
'cert_name']),
74 output_level=dict(type=
'str', default=
'normal', choices=[
'debug',
'info',
'normal']),
75 timeout=dict(type=
'int', default=30),
76 use_proxy=dict(type=
'bool', default=
True),
77 use_ssl=dict(type=
'bool', default=
True),
78 validate_certs=dict(type=
'bool', default=
True),
87 self.
resultresult = dict(changed=
False)
92 self.
errorerror = dict(code=
None, text=
None)
117 if self.
modulemodule._debug:
118 self.
modulemodule.warn(
'Enable debug output because ANSIBLE_DEBUG was set.')
119 self.
paramsparams[
'output_level'] =
'debug'
121 if self.
paramsparams[
'private_key']:
124 self.
modulemodule.
fail_json(msg=
'Cannot use signature-based authentication because pyopenssl is not available')
125 elif self.
paramsparams[
'password']
is not None:
126 self.
modulemodule.warn(
"When doing ACI signatured-based authentication, providing parameter 'password' is not required")
127 elif self.
paramsparams[
'password']:
131 self.
modulemodule.
fail_json(msg=
"Either parameter 'password' or 'private_key' is required for authentication")
133 def boolean(self, value, true='yes', false='no'):
134 ''' Return an acceptable value back '''
145 self.
modulemodule.
fail_json(msg=
"Boolean value '%s' is an invalid ACI boolean value.")
148 ''' Return an ACI-compatible ISO8601 formatted time: 2123-12-12T00:00:00.000+00:00 '''
150 return dt.isoformat(timespec=
'milliseconds')
152 tz = dt.strftime(
'%z')
153 return '%s.%03d%s:%s' % (dt.strftime(
'%Y-%m-%dT%H:%M:%S'), dt.microsecond / 1000, tz[:3], tz[3:])
156 ''' Set protocol based on use_ssl parameter '''
159 self.
paramsparams[
'protocol'] =
'https' if self.
paramsparams.
get(
'use_ssl',
True)
else 'http'
162 ''' Set method based on state parameter '''
165 state_map = dict(absent=
'delete', present=
'post', query=
'get')
166 self.
paramsparams[
'method'] = state_map[self.
paramsparams[
'state']]
169 ''' Log in to APIC '''
172 if 'port' in self.
paramsparams
and self.
paramsparams[
'port']
is not None:
173 url =
'%(protocol)s://%(host)s:%(port)s/api/aaaLogin.json' % self.
paramsparams
175 url =
'%(protocol)s://%(host)s/api/aaaLogin.json' % self.
paramsparams
176 payload = {
'aaaUser': {
'attributes': {
'name': self.
paramsparams[
'username'],
'pwd': self.
paramsparams[
'password']}}}
178 data=json.dumps(payload),
180 timeout=self.
paramsparams[
'timeout'],
181 use_proxy=self.
paramsparams[
'use_proxy'])
184 if auth[
'status'] != 200:
186 self.
statusstatus = auth[
'status']
190 self.
fail_jsonfail_json(msg=
'Authentication failed: %(code)s %(text)s' % self.
errorerror)
193 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % auth)
196 self.
headersheaders[
'Cookie'] = resp.headers[
'Set-Cookie']
198 def cert_auth(self, path=None, payload='', method=None):
199 ''' Perform APIC signature-based authentication, not the expected SSL client certificate authentication. '''
202 method = self.
paramsparams[
'method'].upper()
207 path =
'/' + path.lstrip(
'/')
213 if self.
paramsparams[
'private_key'].startswith(
'-----BEGIN PRIVATE KEY-----'):
217 self.
modulemodule.
fail_json(msg=
"Cannot load provided 'private_key' parameter.")
219 if self.
paramsparams[
'certificate_name']
is None:
220 self.
paramsparams[
'certificate_name'] = self.
paramsparams[
'username']
221 elif self.
paramsparams[
'private_key'].startswith(
'-----BEGIN CERTIFICATE-----'):
222 self.
modulemodule.
fail_json(msg=
"Provided 'private_key' parameter value appears to be a certificate. Please correct.")
226 if not os.path.exists(self.
paramsparams[
'private_key']):
227 self.
modulemodule.
fail_json(msg=
"The provided private key file does not appear to exist. Is it a filename?")
229 with open(self.
paramsparams[
'private_key'],
'r')
as fh:
230 private_key_content = fh.read()
232 self.
modulemodule.
fail_json(msg=
"Cannot open private key file '%s'." % self.
paramsparams[
'private_key'])
233 if private_key_content.startswith(
'-----BEGIN PRIVATE KEY-----'):
237 self.
modulemodule.
fail_json(msg=
"Cannot load private key file '%s'." % self.
paramsparams[
'private_key'])
239 if self.
paramsparams[
'certificate_name']
is None:
240 self.
paramsparams[
'certificate_name'] = os.path.basename(os.path.splitext(self.
paramsparams[
'private_key'])[0])
241 elif private_key_content.startswith(
'-----BEGIN CERTIFICATE-----'):
242 self.
modulemodule.
fail_json(msg=
"Provided private key file %s appears to be a certificate. Please correct." % self.
paramsparams[
'private_key'])
244 self.
modulemodule.
fail_json(msg=
"Provided private key file '%s' does not appear to be a private key. Please correct." % self.
paramsparams[
'private_key'])
247 sig_request = method + path + payload
248 sig_signature = base64.b64encode(
sign(sig_key, sig_request,
'sha256'))
249 sig_dn =
'uni/userext/user-%s/usercert-%s' % (self.
paramsparams[
'username'], self.
paramsparams[
'certificate_name'])
250 self.
headersheaders[
'Cookie'] =
'APIC-Certificate-Algorithm=v1.0; ' +\
251 'APIC-Certificate-DN=%s; ' % sig_dn +\
252 'APIC-Certificate-Fingerprint=fingerprint; ' +\
253 'APIC-Request-Signature=%s' %
to_native(sig_signature)
256 ''' Handle APIC JSON response output '''
258 jsondata = json.loads(rawoutput)
259 except Exception
as e:
261 self.
errorerror = dict(code=-1, text=
"Unable to parse output as JSON, see 'raw' output. %s" % e)
262 self.
resultresult[
'raw'] = rawoutput
267 self.
imdataimdata = jsondata[
'imdata']
269 self.
imdataimdata = dict()
270 self.
totalCounttotalCount = int(jsondata[
'totalCount'])
276 ''' Handle APIC XML response output '''
280 xml = lxml.etree.fromstring(
to_bytes(rawoutput))
281 xmldata = cobra.data(xml)
282 except Exception
as e:
284 self.
errorerror = dict(code=-1, text=
"Unable to parse output as XML, see 'raw' output. %s" % e)
285 self.
resultresult[
'raw'] = rawoutput
290 self.
imdataimdata = xmldata[
'imdata'][
'children']
292 self.
imdataimdata = dict()
293 self.
totalCounttotalCount = int(xmldata[
'imdata'][
'attributes'][
'totalCount'])
299 ''' Set error information when found '''
304 self.
errorerror = self.
imdataimdata[0][
'error'][
'attributes']
305 except (KeyError, IndexError):
309 ''' Perform a REST request '''
315 if 'port' in self.
paramsparams
and self.
paramsparams[
'port']
is not None:
316 self.
urlurl =
'%(protocol)s://%(host)s:%(port)s/' % self.
paramsparams + path.lstrip(
'/')
318 self.
urlurl =
'%(protocol)s://%(host)s/' % self.
paramsparams + path.lstrip(
'/')
321 if self.
paramsparams[
'private_key']:
322 self.
cert_authcert_auth(path=path, payload=payload)
328 method=self.
paramsparams[
'method'].upper(),
329 timeout=self.
paramsparams[
'timeout'],
330 use_proxy=self.
paramsparams[
'use_proxy'])
333 self.
statusstatus = info[
'status']
336 if info[
'status'] != 200:
340 self.
fail_jsonfail_json(msg=
'APIC Error %(code)s: %(text)s' % self.
errorerror)
343 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % info)
348 ''' Perform a query with no payload '''
352 if 'port' in self.
paramsparams
and self.
paramsparams[
'port']
is not None:
353 self.
urlurl =
'%(protocol)s://%(host)s:%(port)s/' % self.
paramsparams + path.lstrip(
'/')
355 self.
urlurl =
'%(protocol)s://%(host)s/' % self.
paramsparams + path.lstrip(
'/')
358 if self.
paramsparams[
'private_key']:
359 self.
cert_authcert_auth(path=path, method=
'GET')
366 timeout=self.
paramsparams[
'timeout'],
367 use_proxy=self.
paramsparams[
'use_proxy'])
370 if query[
'status'] != 200:
371 self.
responseresponse = query[
'msg']
372 self.
statusstatus = query[
'status']
376 self.
fail_jsonfail_json(msg=
'APIC Error %(code)s: %(text)s' % self.
errorerror)
379 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % query)
381 query = json.loads(resp.read())
383 return json.dumps(query[
'imdata'], sort_keys=
True, indent=2) +
'\n'
386 ''' Perform a request, including a proper diff output '''
387 self.
resultresult[
'diff'] = dict()
388 self.
resultresult[
'diff'][
'before'] = self.
queryquery(path)
389 self.
requestrequest(path, payload=payload)
391 self.
resultresult[
'diff'][
'after'] = self.
queryquery(path)
393 if self.
resultresult[
'diff'][
'before'] != self.
resultresult[
'diff'][
'after']:
394 self.
resultresult[
'changed'] =
True
398 ''' Append key-value pairs to self.filter_string '''
399 accepted_params = dict((k, v)
for (k, v)
in params.items()
if v
is not None)
405 self.
filter_stringfilter_string +=
'&'.join([
'%s=%s' % (k, v)
for (k, v)
in accepted_params.items()])
409 ''' Build an APIC filter based on obj_class and key-value pairs '''
410 accepted_params = dict((k, v)
for (k, v)
in params.items()
if v
is not None)
411 if len(accepted_params) == 1:
412 return ','.join(
'eq({0}.{1},"{2}")'.
format(obj_class, k, v)
for (k, v)
in accepted_params.items())
413 elif len(accepted_params) > 1:
414 return 'and(' +
','.join([
'eq({0}.{1},"{2}")'.
format(obj_class, k, v)
for (k, v)
in accepted_params.items()]) +
')'
417 target_class = obj[
'target_class']
418 target_filter = obj[
'target_filter']
419 subtree_class = obj[
'subtree_class']
420 subtree_filter = obj[
'subtree_filter']
421 object_rn = obj[
'object_rn']
422 mo = obj[
'module_object']
423 add_subtree_filter = obj[
'add_subtree_filter']
424 add_target_filter = obj[
'add_target_filter']
426 if self.
modulemodule.params[
'state']
in (
'absent',
'present')
and mo
is not None:
427 self.
pathpath =
'api/mo/uni/{0}.json'.
format(object_rn)
428 self.
update_qsupdate_qs({
'rsp-prop-include':
'config-only'})
432 if object_rn
is not None:
434 self.
pathpath =
'api/mo/uni/{0}.json'.
format(object_rn)
436 self.
pathpath =
'api/class/{0}.json'.
format(target_class)
438 if add_target_filter:
440 {
'query-target-filter': self.
build_filterbuild_filter(target_class, target_filter)})
442 if add_subtree_filter:
444 {
'rsp-subtree-filter': self.
build_filterbuild_filter(subtree_class, subtree_filter)})
446 if 'port' in self.
paramsparams
and self.
paramsparams[
'port']
is not None:
447 self.
urlurl =
'{protocol}://{host}:{port}/{path}'.
format(
448 path=self.
pathpath, **self.
modulemodule.params)
451 self.
urlurl =
'{protocol}://{host}/{path}'.
format(
452 path=self.
pathpath, **self.
modulemodule.params)
456 {
'rsp-subtree':
'full',
'rsp-subtree-class':
','.join(sorted(self.
child_classeschild_classes))})
460 for parent_object
in parent_objects:
461 if parent_object[
'aci_class']
is parent_class:
468 This method is used to retrieve the appropriate URL path
and filter_string to make the request to the APIC.
470 :param target_object: The target
class dictionary containing parent_class, aci_class, aci_rn, target_filter, and module_object keys.
471 :param parent_objects: The parent
class list of dictionaries containing parent_class, aci_class, aci_rn, target_filter, and module_object keys.
472 :param child_classes: The list of child classes that the module supports along
with the object.
473 :type target_object: dict
474 :type parent_objects: list[dict]
475 :type child_classes: list[string]
476 :
return: The path
and filter_string needed to build the full URL.
481 subtree_classes =
None
482 add_subtree_filter =
False
483 add_target_filter =
False
484 has_target_query =
False
485 has_target_query_compare =
False
486 has_target_query_difference =
False
487 has_target_query_called =
False
489 if child_classes
is None:
494 target_parent_class = target_object[
'parent_class']
495 target_class = target_object[
'aci_class']
496 target_rn = target_object[
'aci_rn']
497 target_filter = target_object[
'target_filter']
498 target_module_object = target_object[
'module_object']
500 url_path_object = dict(
501 target_class=target_class,
502 target_filter=target_filter,
503 subtree_class=target_class,
504 subtree_filter=target_filter,
505 module_object=target_module_object
508 if target_module_object
is not None:
509 rn_builder = target_rn
511 has_target_query =
True
512 has_target_query_compare =
True
514 if parent_objects
is not None:
515 current_parent_class = target_parent_class
516 has_parent_query_compare =
False
517 has_parent_query_difference =
False
518 is_first_parent =
True
519 is_single_parent =
None
520 search_classes = set()
522 while current_parent_class !=
'uni':
524 parent_objects=parent_objects, parent_class=current_parent_class)
526 if parent_object
is not None:
527 parent_parent_class = parent_object[
'parent_class']
528 parent_class = parent_object[
'aci_class']
529 parent_rn = parent_object[
'aci_rn']
530 parent_filter = parent_object[
'target_filter']
531 parent_module_object = parent_object[
'module_object']
534 is_single_parent =
True
536 is_single_parent =
False
537 is_first_parent =
False
539 if parent_parent_class !=
'uni':
540 search_classes.add(parent_class)
542 if parent_module_object
is not None:
543 if rn_builder
is not None:
544 rn_builder =
'{0}/{1}'.
format(parent_rn,
547 rn_builder = parent_rn
549 url_path_object[
'target_class'] = parent_class
550 url_path_object[
'target_filter'] = parent_filter
552 has_target_query =
False
555 subtree_classes = search_classes
557 has_target_query =
True
559 has_parent_query_compare =
True
561 current_parent_class = parent_parent_class
563 raise ValueError(
"Reference error for parent_class '{0}'. Each parent_class must reference a valid object".
format(current_parent_class))
565 if not has_target_query_difference
and not has_target_query_called:
566 if has_target_query
is not has_target_query_compare:
567 has_target_query_difference =
True
569 if not has_parent_query_difference
and has_target_query
is not has_parent_query_compare:
570 has_parent_query_difference =
True
571 has_target_query_called =
True
573 if not has_parent_query_difference
and has_parent_query_compare
and target_module_object
is not None:
574 add_target_filter =
True
576 elif has_parent_query_difference
and target_module_object
is not None:
577 add_subtree_filter =
True
581 add_target_filter =
True
583 elif has_parent_query_difference
and not has_target_query
and target_module_object
is None:
587 elif not has_parent_query_difference
and not has_target_query
and target_module_object
is None:
590 elif not has_target_query
and is_single_parent
and target_module_object
is None:
593 url_path_object[
'object_rn'] = rn_builder
594 url_path_object[
'add_subtree_filter'] = add_subtree_filter
595 url_path_object[
'add_target_filter'] = add_target_filter
599 def construct_url(self, root_class, subclass_1=None, subclass_2=None, subclass_3=None, child_classes=None):
601 This method is used to retrieve the appropriate URL path
and filter_string to make the request to the APIC.
603 :param root_class: The top-level
class dictionary containing aci_class, aci_rn, target_filter, and module_object keys.
604 :param sublass_1: The second-level
class dictionary containing aci_class, aci_rn, target_filter, and module_object keys.
605 :param sublass_2: The third-level
class dictionary containing aci_class, aci_rn, target_filter, and module_object keys.
606 :param sublass_3: The fourth-level
class dictionary containing aci_class, aci_rn, target_filter, and module_object keys.
607 :param child_classes: The list of child classes that the module supports along
with the object.
608 :type root_class: dict
609 :type subclass_1: dict
610 :type subclass_2: dict
611 :type subclass_3: dict
612 :type child_classes: list
613 :
return: The path
and filter_string needed to build the full URL.
617 if child_classes
is None:
622 if subclass_3
is not None:
623 self.
_construct_url_4_construct_url_4(root_class, subclass_1, subclass_2, subclass_3)
624 elif subclass_2
is not None:
626 elif subclass_1
is not None:
631 if 'port' in self.
paramsparams
and self.
paramsparams[
'port']
is not None:
632 self.
urlurl =
'{protocol}://{host}:{port}/{path}'.
format(path=self.
pathpath, **self.
modulemodule.params)
634 self.
urlurl =
'{protocol}://{host}/{path}'.
format(path=self.
pathpath, **self.
modulemodule.params)
638 self.
update_qsupdate_qs({
'rsp-subtree':
'full',
'rsp-subtree-class':
','.join(sorted(self.
child_classeschild_classes))})
642 This method is used by construct_url when the object
is the top-level
class.
644 obj_class = obj['aci_class']
645 obj_rn = obj[
'aci_rn']
646 obj_filter = obj[
'target_filter']
647 mo = obj[
'module_object']
649 if self.
modulemodule.params[
'state']
in (
'absent',
'present'):
651 self.
pathpath =
'api/mo/uni/{0}.json'.
format(obj_rn)
652 self.
update_qsupdate_qs({
'rsp-prop-include':
'config-only'})
655 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
656 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
659 self.
pathpath =
'api/mo/uni/{0}.json'.
format(obj_rn)
663 This method is used by construct_url when the object
is the second-level
class.
665 parent_class = parent['aci_class']
666 parent_rn = parent[
'aci_rn']
667 parent_filter = parent[
'target_filter']
668 parent_obj = parent[
'module_object']
669 obj_class = obj[
'aci_class']
670 obj_rn = obj[
'aci_rn']
671 obj_filter = obj[
'target_filter']
672 mo = obj[
'module_object']
674 if self.
modulemodule.params[
'state']
in (
'absent',
'present'):
676 self.
pathpath =
'api/mo/uni/{0}/{1}.json'.
format(parent_rn, obj_rn)
677 self.
update_qsupdate_qs({
'rsp-prop-include':
'config-only'})
678 elif parent_obj
is None and mo
is None:
680 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
681 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
682 elif parent_obj
is None:
684 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
685 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
689 self.
pathpath =
'api/mo/uni/{0}.json'.
format(parent_rn)
692 self.
pathpath =
'api/mo/uni/{0}/{1}.json'.
format(parent_rn, obj_rn)
696 This method is used by construct_url when the object
is the third-level
class.
698 root_class = root['aci_class']
699 root_rn = root[
'aci_rn']
700 root_filter = root[
'target_filter']
701 root_obj = root[
'module_object']
702 parent_class = parent[
'aci_class']
703 parent_rn = parent[
'aci_rn']
704 parent_filter = parent[
'target_filter']
705 parent_obj = parent[
'module_object']
706 obj_class = obj[
'aci_class']
707 obj_rn = obj[
'aci_rn']
708 obj_filter = obj[
'target_filter']
709 mo = obj[
'module_object']
711 if self.
modulemodule.params[
'state']
in (
'absent',
'present'):
713 self.
pathpath =
'api/mo/uni/{0}/{1}/{2}.json'.
format(root_rn, parent_rn, obj_rn)
714 self.
update_qsupdate_qs({
'rsp-prop-include':
'config-only'})
715 elif root_obj
is None and parent_obj
is None and mo
is None:
717 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
718 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
719 elif root_obj
is None and parent_obj
is None:
721 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
722 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
723 elif root_obj
is None and mo
is None:
727 self.
pathpath =
'api/class/{0}.json'.
format(parent_class)
728 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(parent_class, parent_filter)})
729 elif parent_obj
is None and mo
is None:
732 self.
pathpath =
'api/mo/uni/{0}.json'.
format(root_rn)
735 elif root_obj
is None:
739 self.
pathpath =
'api/class/{0}.json'.
format(parent_class)
740 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(parent_class, parent_filter)})
741 self.
update_qsupdate_qs({
'rsp-subtree-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
742 elif parent_obj
is None:
745 self.
pathpath =
'api/mo/uni/{0}.json'.
format(root_rn)
749 self.
update_qsupdate_qs({
'rsp-subtree-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
753 self.
pathpath =
'api/mo/uni/{0}/{1}.json'.
format(root_rn, parent_rn)
758 self.
pathpath =
'api/mo/uni/{0}/{1}/{2}.json'.
format(root_rn, parent_rn, obj_rn)
762 This method is used by construct_url when the object
is the fourth-level
class.
764 root_class = root['aci_class']
765 root_rn = root[
'aci_rn']
766 root_filter = root[
'target_filter']
767 root_obj = root[
'module_object']
768 sec_class = sec[
'aci_class']
769 sec_rn = sec[
'aci_rn']
770 sec_filter = sec[
'target_filter']
771 sec_obj = sec[
'module_object']
772 parent_class = parent[
'aci_class']
773 parent_rn = parent[
'aci_rn']
774 parent_filter = parent[
'target_filter']
775 parent_obj = parent[
'module_object']
776 obj_class = obj[
'aci_class']
777 obj_rn = obj[
'aci_rn']
778 obj_filter = obj[
'target_filter']
779 mo = obj[
'module_object']
784 if self.
modulemodule.params[
'state']
in (
'absent',
'present'):
786 self.
pathpath =
'api/mo/uni/{0}/{1}/{2}/{3}.json'.
format(root_rn, sec_rn, parent_rn, obj_rn)
787 self.
update_qsupdate_qs({
'rsp-prop-include':
'config-only'})
789 elif root_obj
is None:
791 self.
pathpath =
'api/class/{0}.json'.
format(obj_class)
792 self.
update_qsupdate_qs({
'query-target-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
793 elif sec_obj
is None:
795 self.
pathpath =
'api/mo/uni/{0}.json'.
format(root_rn)
799 self.
update_qsupdate_qs({
'rsp-subtree-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
800 elif parent_obj
is None:
802 self.
pathpath =
'api/mo/uni/{0}/{1}.json'.
format(root_rn, sec_rn)
806 self.
update_qsupdate_qs({
'rsp-subtree-filter': self.
build_filterbuild_filter(obj_class, obj_filter)})
809 self.
pathpath =
'api/mo/uni/{0}/{1}/{2}.json'.
format(root_rn, sec_rn, parent_rn)
814 self.
pathpath =
'api/mo/uni/{0}/{1}/{2}/{3}.json'.
format(root_rn, sec_rn, parent_rn, obj_rn)
818 This method is used to handle the logic when the modules state
is equal to absent. The method only pushes a change
if
819 the object exists,
and if check_mode
is False. A successful change will mark the module
as changed.
826 elif not self.
modulemodule.check_mode:
828 if self.
paramsparams[
'private_key']:
834 timeout=self.
paramsparams[
'timeout'],
835 use_proxy=self.
paramsparams[
'use_proxy'])
838 self.
statusstatus = info[
'status']
839 self.
methodmethod =
'DELETE'
842 if info[
'status'] == 200:
843 self.
resultresult[
'changed'] =
True
849 self.
fail_jsonfail_json(msg=
'APIC Error %(code)s: %(text)s' % self.
errorerror)
852 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % info)
854 self.
resultresult[
'changed'] =
True
855 self.
methodmethod =
'DELETE'
859 This method is used to get the difference between the proposed
and existing configurations. Each module
860 should call the get_existing method before this method,
and add the proposed config to the module results
861 using the module
's config parameters. The new config will added to the self.result dictionary.
863 :param aci_class: Type str.
864 This is the root dictionary key
for the MO
's configuration body, or the ACI class of the MO.
866 proposed_config = self.proposedproposed[aci_class]['attributes']
868 existing_config = self.
existingexisting[0][aci_class][
'attributes']
872 for key, value
in proposed_config.items():
873 existing_field = existing_config.get(key)
874 if value != existing_field:
881 config = {aci_class: {
'attributes': config}}
885 if children
and config:
886 config[aci_class].
update({
'children': children})
888 config = {aci_class: {
'attributes': {},
'children': children}}
893 self.
configconfig = config
898 This method is used to get the difference between a proposed
and existing child configs. The
get_nested_config()
899 method should be used to
return the proposed
and existing config portions of child.
901 :param child_class: Type str.
902 The root
class (dict key)
for the child dictionary.
903 :param proposed_child: Type dict.
904 The config portion of the proposed child dictionary.
905 :param existing_child: Type dict.
906 The config portion of the existing child dictionary.
907 :
return: The child config
with only values that are updated. If the proposed dictionary has no updates to make
908 to what exists on the APIC, then
None is returned.
910 update_config = {child_class: {'attributes': {}}}
911 for key, value
in proposed_child.items():
912 existing_field = existing_child.get(key)
913 if value != existing_field:
914 update_config[child_class][
'attributes'][key] = value
916 if not update_config[child_class][
'attributes']:
923 This method is used to retrieve the updated child configs by comparing the proposed children configs
924 agains the objects existing children configs.
926 :param aci_class: Type str.
927 This
is the root dictionary key
for the MO
's configuration body, or the ACI class of the MO.
928 :return: The list of updated child config dictionaries.
None is returned
if there are no changes to the child
931 proposed_children = self.proposedproposed[aci_class].get('children')
932 if proposed_children:
934 existing_children = self.
existingexisting[0][aci_class].
get(
'children', [])
937 for child
in proposed_children:
938 child_class, proposed_child, existing_child = self.
get_nested_configget_nested_config(child, existing_children)
940 if existing_child
is None:
943 child_update = self.
get_diff_childget_diff_child(child_class, proposed_child, existing_child)
947 child_updates.append(child_update)
955 This method is used to get the existing object(s) based on the path specified
in the module. Each module should
956 build the URL so that
if the object
's name is supplied, then it will retrieve the configuration for that particular
957 object, but if no name
is supplied, then it will retrieve all MOs
for the
class. Following this method will ensure
958 that this method can be used to supply the existing configuration when using the get_diff method. The response, status,
959 and existing configuration will be added to the self.
resultresult dictionary.
964 if self.
paramsparams[
'private_key']:
970 timeout=self.
paramsparams[
'timeout'],
971 use_proxy=self.
paramsparams[
'use_proxy'])
973 self.
statusstatus = info[
'status']
977 if info[
'status'] == 200:
978 self.
existingexisting = json.loads(resp.read())[
'imdata']
983 self.
fail_jsonfail_json(msg=
'APIC Error %(code)s: %(text)s' % self.
errorerror)
986 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % info)
991 This method is used
for stiping off the outer layers of the child dictionaries so only the configuration
992 key, value pairs are returned.
994 :param proposed_child: Type dict.
995 The dictionary that represents the child config.
996 :param existing_children: Type list.
997 The list of existing child config dictionaries.
998 :
return: The child
's class as str (root config dict key), the child's proposed config dict,
and the child
's
999 existing configuration dict.
1001 for key
in proposed_child.keys():
1003 proposed_config = proposed_child[key][
'attributes']
1004 existing_config =
None
1008 for child
in existing_children:
1009 if child.get(child_class):
1010 existing_config = child[key][
'attributes']
1013 if set(proposed_config.items()).
issubset(set(existing_config.items())):
1016 return child_class, proposed_config, existing_config
1018 def payload(self, aci_class, class_config, child_configs=None):
1020 This method is used to dynamically build the proposed configuration dictionary
from the config related parameters
1021 passed into the module. All values that were
not passed values
from the playbook task will be removed so
as to
not
1022 inadvertently change configurations.
1024 :param aci_class: Type str
1025 This
is the root dictionary key
for the MO
's configuration body, or the ACI class of the MO.
1026 :param class_config: Type dict
1027 This is the configuration of the MO using the dictionary keys expected by the API
1028 :param child_configs: Type list
1029 This
is a list of child dictionaries associated
with the MOs config. The list should only
1030 include child objects that are used to associate two MOs together. Children that represent
1031 MOs should have their own module.
1033 proposed = dict((k, str(v)) for k, v
in class_config.items()
if v
is not None)
1034 self.
proposedproposed = {aci_class: {
'attributes': proposed}}
1039 for child
in child_configs:
1040 child_copy = deepcopy(child)
1042 for root_key
in child_copy.keys():
1043 for final_keys, values
in child_copy[root_key][
'attributes'].items():
1045 child[root_key][
'attributes'].pop(final_keys)
1047 child[root_key][
'attributes'][final_keys] = str(values)
1050 children.append(child)
1057 This method is used to handle the logic when the modules state
is equal to present. The method only pushes a change
if
1058 the object has differences than what exists on the APIC,
and if check_mode
is False. A successful change will mark the
1061 if not self.
configconfig:
1063 elif not self.
modulemodule.check_mode:
1065 if self.
paramsparams[
'private_key']:
1066 self.
cert_authcert_auth(method=
'POST', payload=json.dumps(self.
configconfig))
1069 data=json.dumps(self.
configconfig),
1072 timeout=self.
paramsparams[
'timeout'],
1073 use_proxy=self.
paramsparams[
'use_proxy'])
1075 self.
responseresponse = info[
'msg']
1076 self.
statusstatus = info[
'status']
1077 self.
methodmethod =
'POST'
1080 if info[
'status'] == 200:
1081 self.
resultresult[
'changed'] =
True
1087 self.
fail_jsonfail_json(msg=
'APIC Error %(code)s: %(text)s' % self.
errorerror)
1090 self.
fail_jsonfail_json(msg=
'Connection failed for %(url)s. %(msg)s' % info)
1092 self.
resultresult[
'changed'] =
True
1093 self.
methodmethod =
'POST'
1097 if 'state' in self.
paramsparams:
1098 if self.
paramsparams[
'state']
in (
'absent',
'present'):
1099 if self.
paramsparams[
'output_level']
in (
'debug',
'info'):
1103 if self.
paramsparams[
'output_level'] ==
'debug':
1104 if 'state' in self.
paramsparams:
1112 if 'state' in self.
paramsparams:
1114 if self.
paramsparams[
'state']
in (
'absent',
'present'):
1124 if self.
paramsparams[
'output_level']
in (
'debug',
'info'):
1134 if self.
errorerror[
'code']
is not None and self.
errorerror[
'text']
is not None:
1137 if 'state' in self.
paramsparams:
1138 if self.
paramsparams[
'state']
in (
'absent',
'present'):
1139 if self.
paramsparams[
'output_level']
in (
'debug',
'info'):
1143 if self.
paramsparams[
'output_level'] ==
'debug':
1144 if self.
imdataimdata
is not None:
1148 if self.
paramsparams[
'output_level'] ==
'debug':
1149 if self.
urlurl
is not None:
1150 if 'state' in self.
paramsparams:
1158 if 'state' in self.
paramsparams:
1159 if self.
paramsparams[
'output_level']
in (
'debug',
'info'):
def iso8601_format(self, dt)
def _deep_url_parent_object(self, parent_objects, parent_class)
def __init__(self, module)
def get_diff_child(child_class, proposed_child, existing_child)
def _construct_url_1(self, obj)
def get_diff(self, aci_class)
def get_nested_config(proposed_child, existing_children)
def _construct_url_4(self, root, sec, parent, obj)
def update_qs(self, params)
def build_filter(self, obj_class, params)
def response_xml(self, rawoutput)
def get_diff_children(self, aci_class)
def response_json(self, rawoutput)
def payload(self, aci_class, class_config, child_configs=None)
def _deep_url_path_builder(self, obj)
def fail_json(self, msg, **kwargs)
def define_protocol(self)
def construct_url(self, root_class, subclass_1=None, subclass_2=None, subclass_3=None, child_classes=None)
def boolean(self, value, true='yes', false='no')
def _construct_url_3(self, root, parent, obj)
def cert_auth(self, path=None, payload='', method=None)
def request(self, path, payload=None)
def request_diff(self, path, payload=None)
def exit_json(self, **kwargs)
def construct_deep_url(self, target_object, parent_objects=None, child_classes=None)
def _construct_url_2(self, parent, obj)
def to_bytes(obj, encoding='utf-8', errors=None, nonstring='simplerepr')
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl')
def issubset(subset, superset)
def get(network_os, module_name, connection_type)
def fetch_url(module, url, data=None, headers=None, method=None, use_proxy=True, force=False, last_mod_time=None, timeout=10, use_gssapi=False, unix_socket=None, ca_path=None, cookies=None)
def update(client, current_stream, stream_name, number_of_shards=1, retention_period=None, tags=None, wait=False, wait_timeout=300, check_mode=False)