diff --git a/lib/ansible/module_utils/network/aci/aci.py b/lib/ansible/module_utils/network/aci/aci.py index 81cbdd68bbc..9c1adf5f87c 100644 --- a/lib/ansible/module_utils/network/aci/aci.py +++ b/lib/ansible/module_utils/network/aci/aci.py @@ -10,6 +10,7 @@ # Copyright: (c) 2017, Dag Wieers # Copyright: (c) 2017, Jacob McGill (@jmcgill298) # Copyright: (c) 2017, Swetha Chunduri (@schunduri) +# Copyright: (c) 2019, Rob Huelga (@RobW3LGA) # All rights reserved. # Redistribution and use in source and binary forms, with or without modification, @@ -412,6 +413,189 @@ class ACIModule(object): elif len(accepted_params) > 1: return 'and(' + ','.join(['eq({0}.{1}, "{2}")'.format(obj_class, k, v) for (k, v) in accepted_params.items()]) + ')' + def _deep_url_path_builder(self, obj): + target_class = obj['target_class'] + target_filter = obj['target_filter'] + subtree_class = obj['subtree_class'] + subtree_filter = obj['subtree_filter'] + object_rn = obj['object_rn'] + mo = obj['module_object'] + add_subtree_filter = obj['add_subtree_filter'] + add_target_filter = obj['add_target_filter'] + + if self.module.params['state'] in ('absent', 'present') and mo is not None: + self.path = 'api/mo/uni/{0}.json'.format(object_rn) + self.update_qs({'rsp-prop-include': 'config-only'}) + + else: + # State is 'query' + if object_rn is not None: + # Query for a specific object in the module's class + self.path = 'api/mo/uni/{0}.json'.format(object_rn) + else: + self.path = 'api/class/{0}.json'.format(target_class) + + if add_target_filter: + self.update_qs( + {'query-target-filter': self.build_filter(target_class, target_filter)}) + + if add_subtree_filter: + self.update_qs( + {'rsp-subtree-filter': self.build_filter(subtree_class, subtree_filter)}) + + if 'port' in self.params and self.params['port'] is not None: + self.url = '{protocol}://{host}:{port}/{path}'.format( + path=self.path, **self.module.params) + + else: + self.url = '{protocol}://{host}/{path}'.format( + path=self.path, **self.module.params) + + if self.child_classes: + self.update_qs( + {'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(sorted(self.child_classes))}) + + def _deep_url_parent_object(self, parent_objects, parent_class): + + for parent_object in parent_objects: + if parent_object['aci_class'] is parent_class: + return parent_object + + return None + + def construct_deep_url(self, target_object, parent_objects=None, child_classes=None): + """ + This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC. + + :param target_object: The target class dictionary containing parent_class, aci_class, aci_rn, target_filter, and module_object keys. + :param parent_objects: The parent class list of dictionaries containing parent_class, aci_class, aci_rn, target_filter, and module_object keys. + :param child_classes: The list of child classes that the module supports along with the object. + :type target_object: dict + :type parent_objects: list[dict] + :type child_classes: list[string] + :return: The path and filter_string needed to build the full URL. + """ + + self.filter_string = '' + rn_builder = None + subtree_classes = None + add_subtree_filter = False + add_target_filter = False + has_target_query = False + has_target_query_compare = False + has_target_query_difference = False + has_target_query_called = False + + if child_classes is None: + self.child_classes = set() + else: + self.child_classes = set(child_classes) + + target_parent_class = target_object['parent_class'] + target_class = target_object['aci_class'] + target_rn = target_object['aci_rn'] + target_filter = target_object['target_filter'] + target_module_object = target_object['module_object'] + + url_path_object = dict( + target_class=target_class, + target_filter=target_filter, + subtree_class=target_class, + subtree_filter=target_filter, + module_object=target_module_object + ) + + if target_module_object is not None: + rn_builder = target_rn + else: + has_target_query = True + has_target_query_compare = True + + if parent_objects is not None: + current_parent_class = target_parent_class + has_parent_query_compare = False + has_parent_query_difference = False + is_first_parent = True + is_single_parent = None + search_classes = set() + + while current_parent_class != 'uni': + parent_object = self._deep_url_parent_object( + parent_objects=parent_objects, parent_class=current_parent_class) + + if parent_object is not None: + parent_parent_class = parent_object['parent_class'] + parent_class = parent_object['aci_class'] + parent_rn = parent_object['aci_rn'] + parent_filter = parent_object['target_filter'] + parent_module_object = parent_object['module_object'] + + if is_first_parent: + is_single_parent = True + else: + is_single_parent = False + is_first_parent = False + + if parent_parent_class != 'uni': + search_classes.add(parent_class) + + if parent_module_object is not None: + if rn_builder is not None: + rn_builder = '{0}/{1}'.format(parent_rn, + rn_builder) + else: + rn_builder = parent_rn + + url_path_object['target_class'] = parent_class + url_path_object['target_filter'] = parent_filter + + has_target_query = False + else: + rn_builder = None + subtree_classes = search_classes + + has_target_query = True + if is_single_parent: + has_parent_query_compare = True + + current_parent_class = parent_parent_class + else: + raise ValueError("Reference error for parent_class '{0}'. Each parent_class must reference a valid object".format(current_parent_class)) + + if not has_target_query_difference and not has_target_query_called: + if has_target_query is not has_target_query_compare: + has_target_query_difference = True + else: + if not has_parent_query_difference and has_target_query is not has_parent_query_compare: + has_parent_query_difference = True + has_target_query_called = True + + if not has_parent_query_difference and has_parent_query_compare and target_module_object is not None: + add_target_filter = True + + elif has_parent_query_difference and target_module_object is not None: + add_subtree_filter = True + self.child_classes.add(target_class) + + if has_target_query: + add_target_filter = True + + elif has_parent_query_difference and not has_target_query and target_module_object is None: + self.child_classes.add(target_class) + self.child_classes.update(subtree_classes) + + elif not has_parent_query_difference and not has_target_query and target_module_object is None: + self.child_classes.add(target_class) + + elif not has_target_query and is_single_parent and target_module_object is None: + self.child_classes.add(target_class) + + url_path_object['object_rn'] = rn_builder + url_path_object['add_subtree_filter'] = add_subtree_filter + url_path_object['add_target_filter'] = add_target_filter + + self._deep_url_path_builder(url_path_object) + def construct_url(self, root_class, subclass_1=None, subclass_2=None, subclass_3=None, child_classes=None): """ This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC. @@ -451,7 +635,7 @@ class ACIModule(object): if self.child_classes: # Append child_classes to filter_string if filter string is empty - self.update_qs({'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(self.child_classes)}) + self.update_qs({'rsp-subtree': 'full', 'rsp-subtree-class': ','.join(sorted(self.child_classes))}) def _construct_url_1(self, obj): """