1# Copyright (c) 2018 Cisco and/or its affiliates. 2# 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17# 18import re 19 20from ansible.module_utils._text import to_text 21from ansible.module_utils.common.collections import is_string 22from ansible.module_utils.six import iteritems 23 24INVALID_IDENTIFIER_SYMBOLS = r'[^a-zA-Z0-9_]' 25 26IDENTITY_PROPERTIES = ['id', 'version', 'ruleId'] 27NON_COMPARABLE_PROPERTIES = IDENTITY_PROPERTIES + ['isSystemDefined', 'links'] 28 29 30class HTTPMethod: 31 GET = 'get' 32 POST = 'post' 33 PUT = 'put' 34 DELETE = 'delete' 35 36 37class ResponseParams: 38 SUCCESS = 'success' 39 STATUS_CODE = 'status_code' 40 RESPONSE = 'response' 41 42 43class FtdConfigurationError(Exception): 44 def __init__(self, msg, obj=None): 45 super(FtdConfigurationError, self).__init__(msg) 46 self.msg = msg 47 self.obj = obj 48 49 50class FtdServerError(Exception): 51 def __init__(self, response, code): 52 super(FtdServerError, self).__init__(response) 53 self.response = response 54 self.code = code 55 56 57class FtdUnexpectedResponse(Exception): 58 """The exception to be raised in case of unexpected responses from 3d parties.""" 59 pass 60 61 62def construct_ansible_facts(response, params): 63 facts = dict() 64 if response: 65 response_body = response['items'] if 'items' in response else response 66 if params.get('register_as'): 67 facts[params['register_as']] = response_body 68 elif type(response_body) is dict and response_body.get('name') and response_body.get('type'): 69 object_name = re.sub(INVALID_IDENTIFIER_SYMBOLS, '_', response_body['name'].lower()) 70 fact_name = '%s_%s' % (response_body['type'], object_name) 71 facts[fact_name] = response_body 72 return facts 73 74 75def copy_identity_properties(source_obj, dest_obj): 76 for property_name in IDENTITY_PROPERTIES: 77 if property_name in source_obj: 78 dest_obj[property_name] = source_obj[property_name] 79 return dest_obj 80 81 82def is_object_ref(d): 83 """ 84 Checks if a dictionary is a reference object. The dictionary is considered to be a 85 reference object when it contains non-empty 'id' and 'type' fields. 86 87 :type d: dict 88 :return: True if passed dictionary is a reference object, otherwise False 89 """ 90 has_id = 'id' in d.keys() and d['id'] 91 has_type = 'type' in d.keys() and d['type'] 92 return has_id and has_type 93 94 95def equal_object_refs(d1, d2): 96 """ 97 Checks whether two references point to the same object. 98 99 :type d1: dict 100 :type d2: dict 101 :return: True if passed references point to the same object, otherwise False 102 """ 103 have_equal_ids = d1['id'] == d2['id'] 104 have_equal_types = d1['type'] == d2['type'] 105 return have_equal_ids and have_equal_types 106 107 108def equal_lists(l1, l2): 109 """ 110 Checks whether two lists are equal. The order of elements in the arrays is important. 111 112 :type l1: list 113 :type l2: list 114 :return: True if passed lists, their elements and order of elements are equal. Otherwise, returns False. 115 """ 116 if len(l1) != len(l2): 117 return False 118 119 for v1, v2 in zip(l1, l2): 120 if not equal_values(v1, v2): 121 return False 122 123 return True 124 125 126def equal_dicts(d1, d2, compare_by_reference=True): 127 """ 128 Checks whether two dictionaries are equal. If `compare_by_reference` is set to True, dictionaries referencing 129 objects are compared using `equal_object_refs` method. Otherwise, every key and value is checked. 130 131 :type d1: dict 132 :type d2: dict 133 :param compare_by_reference: if True, dictionaries referencing objects are compared using `equal_object_refs` method 134 :return: True if passed dicts are equal. Otherwise, returns False. 135 """ 136 if compare_by_reference and is_object_ref(d1) and is_object_ref(d2): 137 return equal_object_refs(d1, d2) 138 139 if len(d1) != len(d2): 140 return False 141 142 for key, v1 in d1.items(): 143 if key not in d2: 144 return False 145 146 v2 = d2[key] 147 if not equal_values(v1, v2): 148 return False 149 150 return True 151 152 153def equal_values(v1, v2): 154 """ 155 Checks whether types and content of two values are the same. In case of complex objects, the method might be 156 called recursively. 157 158 :param v1: first value 159 :param v2: second value 160 :return: True if types and content of passed values are equal. Otherwise, returns False. 161 :rtype: bool 162 """ 163 164 # string-like values might have same text but different types, so checking them separately 165 if is_string(v1) and is_string(v2): 166 return to_text(v1) == to_text(v2) 167 168 if type(v1) != type(v2): 169 return False 170 value_type = type(v1) 171 172 if value_type == list: 173 return equal_lists(v1, v2) 174 elif value_type == dict: 175 return equal_dicts(v1, v2) 176 else: 177 return v1 == v2 178 179 180def equal_objects(d1, d2): 181 """ 182 Checks whether two objects are equal. Ignores special object properties (e.g. 'id', 'version') and 183 properties with None and empty values. In case properties contains a reference to the other object, 184 only object identities (ids and types) are checked. Also, if an array field contains multiple references 185 to the same object, duplicates are ignored when comparing objects. 186 187 :type d1: dict 188 :type d2: dict 189 :return: True if passed objects and their properties are equal. Otherwise, returns False. 190 """ 191 192 def prepare_data_for_comparison(d): 193 d = dict((k, d[k]) for k in d.keys() if k not in NON_COMPARABLE_PROPERTIES and d[k]) 194 d = delete_ref_duplicates(d) 195 return d 196 197 d1 = prepare_data_for_comparison(d1) 198 d2 = prepare_data_for_comparison(d2) 199 return equal_dicts(d1, d2, compare_by_reference=False) 200 201 202def delete_ref_duplicates(d): 203 """ 204 Removes reference duplicates from array fields: if an array contains multiple items and some of 205 them refer to the same object, only unique references are preserved (duplicates are removed). 206 207 :param d: dict with data 208 :type d: dict 209 :return: dict without reference duplicates 210 """ 211 212 def delete_ref_duplicates_from_list(refs): 213 if all(type(i) == dict and is_object_ref(i) for i in refs): 214 unique_refs = set() 215 unique_list = list() 216 for i in refs: 217 key = (i['id'], i['type']) 218 if key not in unique_refs: 219 unique_refs.add(key) 220 unique_list.append(i) 221 222 return list(unique_list) 223 224 else: 225 return refs 226 227 if not d: 228 return d 229 230 modified_d = {} 231 for k, v in iteritems(d): 232 if type(v) == list: 233 modified_d[k] = delete_ref_duplicates_from_list(v) 234 elif type(v) == dict: 235 modified_d[k] = delete_ref_duplicates(v) 236 else: 237 modified_d[k] = v 238 return modified_d 239