1# -*- coding: utf-8 -*- 2# This code is part of Ansible, but is an independent component. 3# This particular file snippet, and this file snippet only, is BSD licensed. 4# Modules you write using this snippet, which is embedded dynamically by Ansible 5# still belong to the author of the module, and may assign their own license 6# to the complete work. 7# 8# (c) 2018 Red Hat Inc. 9# 10# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) 11 12from __future__ import (absolute_import, division, print_function) 13__metaclass__ = type 14 15 16import os 17from functools import partial 18from ansible.module_utils.common.text.converters import to_native 19from ansible.module_utils.six import iteritems 20from ansible.module_utils.common.text.converters import to_text 21from ansible.module_utils.basic import env_fallback 22from ansible.module_utils.common.validation import check_type_dict 23 24try: 25 from infoblox_client.connector import Connector 26 from infoblox_client.exceptions import InfobloxException 27 HAS_INFOBLOX_CLIENT = True 28except ImportError: 29 HAS_INFOBLOX_CLIENT = False 30 31# defining nios constants 32NIOS_DNS_VIEW = 'view' 33NIOS_NETWORK_VIEW = 'networkview' 34NIOS_HOST_RECORD = 'record:host' 35NIOS_IPV4_NETWORK = 'network' 36NIOS_IPV6_NETWORK = 'ipv6network' 37NIOS_ZONE = 'zone_auth' 38NIOS_PTR_RECORD = 'record:ptr' 39NIOS_A_RECORD = 'record:a' 40NIOS_AAAA_RECORD = 'record:aaaa' 41NIOS_CNAME_RECORD = 'record:cname' 42NIOS_MX_RECORD = 'record:mx' 43NIOS_SRV_RECORD = 'record:srv' 44NIOS_NAPTR_RECORD = 'record:naptr' 45NIOS_TXT_RECORD = 'record:txt' 46NIOS_NSGROUP = 'nsgroup' 47NIOS_IPV4_FIXED_ADDRESS = 'fixedaddress' 48NIOS_IPV6_FIXED_ADDRESS = 'ipv6fixedaddress' 49NIOS_NEXT_AVAILABLE_IP = 'func:nextavailableip' 50NIOS_IPV4_NETWORK_CONTAINER = 'networkcontainer' 51NIOS_IPV6_NETWORK_CONTAINER = 'ipv6networkcontainer' 52NIOS_MEMBER = 'member' 53 54NIOS_PROVIDER_SPEC = { 55 'host': dict(fallback=(env_fallback, ['INFOBLOX_HOST'])), 56 'username': dict(fallback=(env_fallback, ['INFOBLOX_USERNAME'])), 57 'password': dict(fallback=(env_fallback, ['INFOBLOX_PASSWORD']), no_log=True), 58 'validate_certs': dict(type='bool', default=False, fallback=(env_fallback, ['INFOBLOX_SSL_VERIFY']), aliases=['ssl_verify']), 59 'silent_ssl_warnings': dict(type='bool', default=True), 60 'http_request_timeout': dict(type='int', default=10, fallback=(env_fallback, ['INFOBLOX_HTTP_REQUEST_TIMEOUT'])), 61 'http_pool_connections': dict(type='int', default=10), 62 'http_pool_maxsize': dict(type='int', default=10), 63 'max_retries': dict(type='int', default=3, fallback=(env_fallback, ['INFOBLOX_MAX_RETRIES'])), 64 'wapi_version': dict(default='2.1', fallback=(env_fallback, ['INFOBLOX_WAP_VERSION'])), 65 'max_results': dict(type='int', default=1000, fallback=(env_fallback, ['INFOBLOX_MAX_RETRIES'])) 66} 67 68 69def get_connector(*args, **kwargs): 70 ''' Returns an instance of infoblox_client.connector.Connector 71 :params args: positional arguments are silently ignored 72 :params kwargs: dict that is passed to Connector init 73 :returns: Connector 74 ''' 75 if not HAS_INFOBLOX_CLIENT: 76 raise Exception('infoblox-client is required but does not appear ' 77 'to be installed. It can be installed using the ' 78 'command `pip install infoblox-client`') 79 80 if not set(kwargs.keys()).issubset(list(NIOS_PROVIDER_SPEC.keys()) + ['ssl_verify']): 81 raise Exception('invalid or unsupported keyword argument for connector') 82 for key, value in iteritems(NIOS_PROVIDER_SPEC): 83 if key not in kwargs: 84 # apply default values from NIOS_PROVIDER_SPEC since we cannot just 85 # assume the provider values are coming from AnsibleModule 86 if 'default' in value: 87 kwargs[key] = value['default'] 88 89 # override any values with env variables unless they were 90 # explicitly set 91 env = ('INFOBLOX_%s' % key).upper() 92 if env in os.environ: 93 kwargs[key] = os.environ.get(env) 94 95 if 'validate_certs' in kwargs.keys(): 96 kwargs['ssl_verify'] = kwargs['validate_certs'] 97 kwargs.pop('validate_certs', None) 98 99 return Connector(kwargs) 100 101 102def normalize_extattrs(value): 103 ''' Normalize extattrs field to expected format 104 The module accepts extattrs as key/value pairs. This method will 105 transform the key/value pairs into a structure suitable for 106 sending across WAPI in the format of: 107 extattrs: { 108 key: { 109 value: <value> 110 } 111 } 112 ''' 113 return dict([(k, {'value': v}) for k, v in iteritems(value)]) 114 115 116def flatten_extattrs(value): 117 ''' Flatten the key/value struct for extattrs 118 WAPI returns extattrs field as a dict in form of: 119 extattrs: { 120 key: { 121 value: <value> 122 } 123 } 124 This method will flatten the structure to: 125 extattrs: { 126 key: value 127 } 128 ''' 129 return dict([(k, v['value']) for k, v in iteritems(value)]) 130 131 132def member_normalize(member_spec): 133 ''' Transforms the member module arguments into a valid WAPI struct 134 This function will transform the arguments into a structure that 135 is a valid WAPI structure in the format of: 136 { 137 key: <value>, 138 } 139 It will remove any arguments that are set to None since WAPI will error on 140 that condition. 141 The remainder of the value validation is performed by WAPI 142 Some parameters in ib_spec are passed as a list in order to pass the validation for elements. 143 In this function, they are converted to dictionary. 144 ''' 145 member_elements = ['vip_setting', 'ipv6_setting', 'lan2_port_setting', 'mgmt_port_setting', 146 'pre_provisioning', 'network_setting', 'v6_network_setting', 147 'ha_port_setting', 'lan_port_setting', 'lan2_physical_setting', 148 'lan_ha_port_setting', 'mgmt_network_setting', 'v6_mgmt_network_setting'] 149 for key in list(member_spec.keys()): 150 if key in member_elements and member_spec[key] is not None: 151 member_spec[key] = member_spec[key][0] 152 if isinstance(member_spec[key], dict): 153 member_spec[key] = member_normalize(member_spec[key]) 154 elif isinstance(member_spec[key], list): 155 for x in member_spec[key]: 156 if isinstance(x, dict): 157 x = member_normalize(x) 158 elif member_spec[key] is None: 159 del member_spec[key] 160 return member_spec 161 162 163def normalize_ib_spec(ib_spec): 164 result = {} 165 for arg in ib_spec: 166 result[arg] = dict([(k, v) 167 for k, v in iteritems(ib_spec[arg]) 168 if k not in ('ib_req', 'transform', 'update')]) 169 return result 170 171 172class WapiBase(object): 173 ''' Base class for implementing Infoblox WAPI API ''' 174 provider_spec = {'provider': dict(type='dict', options=NIOS_PROVIDER_SPEC)} 175 176 def __init__(self, provider): 177 self.connector = get_connector(**provider) 178 179 def __getattr__(self, name): 180 try: 181 return self.__dict__[name] 182 except KeyError: 183 if name.startswith('_'): 184 raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) 185 return partial(self._invoke_method, name) 186 187 def _invoke_method(self, name, *args, **kwargs): 188 try: 189 method = getattr(self.connector, name) 190 return method(*args, **kwargs) 191 except InfobloxException as exc: 192 if hasattr(self, 'handle_exception'): 193 self.handle_exception(name, exc) 194 else: 195 raise 196 197 198class WapiLookup(WapiBase): 199 ''' Implements WapiBase for lookup plugins ''' 200 def handle_exception(self, method_name, exc): 201 if ('text' in exc.response): 202 raise Exception(exc.response['text']) 203 else: 204 raise Exception(exc) 205 206 207class WapiInventory(WapiBase): 208 ''' Implements WapiBase for dynamic inventory script ''' 209 pass 210 211 212class WapiModule(WapiBase): 213 ''' Implements WapiBase for executing a NIOS module ''' 214 def __init__(self, module): 215 self.module = module 216 provider = module.params['provider'] 217 try: 218 super(WapiModule, self).__init__(provider) 219 except Exception as exc: 220 self.module.fail_json(msg=to_text(exc)) 221 222 def handle_exception(self, method_name, exc): 223 ''' Handles any exceptions raised 224 This method will be called if an InfobloxException is raised for 225 any call to the instance of Connector and also, in case of generic 226 exception. This method will then gracefully fail the module. 227 :args exc: instance of InfobloxException 228 ''' 229 if ('text' in exc.response): 230 self.module.fail_json( 231 msg=exc.response['text'], 232 type=exc.response['Error'].split(':')[0], 233 code=exc.response.get('code'), 234 operation=method_name 235 ) 236 else: 237 self.module.fail_json(msg=to_native(exc)) 238 239 def run(self, ib_obj_type, ib_spec): 240 ''' Runs the module and performans configuration tasks 241 :args ib_obj_type: the WAPI object type to operate against 242 :args ib_spec: the specification for the WAPI object as a dict 243 :returns: a results dict 244 ''' 245 246 update = new_name = None 247 state = self.module.params['state'] 248 if state not in ('present', 'absent'): 249 self.module.fail_json(msg='state must be one of `present`, `absent`, got `%s`' % state) 250 251 result = {'changed': False} 252 253 obj_filter = dict([(k, self.module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')]) 254 255 # get object reference 256 ib_obj_ref, update, new_name = self.get_object_ref(self.module, ib_obj_type, obj_filter, ib_spec) 257 proposed_object = {} 258 for key, value in iteritems(ib_spec): 259 if self.module.params[key] is not None: 260 if 'transform' in value: 261 proposed_object[key] = value['transform'](self.module) 262 else: 263 proposed_object[key] = self.module.params[key] 264 265 # If configure_by_dns is set to False and view is 'default', then delete the default dns 266 if not proposed_object.get('configure_for_dns') and proposed_object.get('view') == 'default'\ 267 and ib_obj_type == NIOS_HOST_RECORD: 268 del proposed_object['view'] 269 270 if ib_obj_ref: 271 if len(ib_obj_ref) > 1: 272 for each in ib_obj_ref: 273 # To check for existing A_record with same name with input A_record by IP 274 if each.get('ipv4addr') and each.get('ipv4addr') == proposed_object.get('ipv4addr'): 275 current_object = each 276 # To check for existing Host_record with same name with input Host_record by IP 277 elif each.get('ipv4addrs')[0].get('ipv4addr') and each.get('ipv4addrs')[0].get('ipv4addr')\ 278 == proposed_object.get('ipv4addrs')[0].get('ipv4addr'): 279 current_object = each 280 # Else set the current_object with input value 281 else: 282 current_object = obj_filter 283 ref = None 284 else: 285 current_object = ib_obj_ref[0] 286 if 'extattrs' in current_object: 287 current_object['extattrs'] = flatten_extattrs(current_object['extattrs']) 288 if current_object.get('_ref'): 289 ref = current_object.pop('_ref') 290 else: 291 current_object = obj_filter 292 ref = None 293 # checks if the object type is member to normalize the attributes being passed 294 if (ib_obj_type == NIOS_MEMBER): 295 proposed_object = member_normalize(proposed_object) 296 297 # checks if the name's field has been updated 298 if update and new_name: 299 proposed_object['name'] = new_name 300 301 check_remove = [] 302 if (ib_obj_type == NIOS_HOST_RECORD): 303 # this check is for idempotency, as if the same ip address shall be passed 304 # add param will be removed, and same exists true for remove case as well. 305 if 'ipv4addrs' in [current_object and proposed_object]: 306 for each in current_object['ipv4addrs']: 307 if each['ipv4addr'] == proposed_object['ipv4addrs'][0]['ipv4addr']: 308 if 'add' in proposed_object['ipv4addrs'][0]: 309 del proposed_object['ipv4addrs'][0]['add'] 310 break 311 check_remove += each.values() 312 if proposed_object['ipv4addrs'][0]['ipv4addr'] not in check_remove: 313 if 'remove' in proposed_object['ipv4addrs'][0]: 314 del proposed_object['ipv4addrs'][0]['remove'] 315 316 res = None 317 modified = not self.compare_objects(current_object, proposed_object) 318 if 'extattrs' in proposed_object: 319 proposed_object['extattrs'] = normalize_extattrs(proposed_object['extattrs']) 320 321 # Checks if nios_next_ip param is passed in ipv4addrs/ipv4addr args 322 proposed_object = self.check_if_nios_next_ip_exists(proposed_object) 323 324 if state == 'present': 325 if ref is None: 326 if not self.module.check_mode: 327 self.create_object(ib_obj_type, proposed_object) 328 result['changed'] = True 329 # Check if NIOS_MEMBER and the flag to call function create_token is set 330 elif (ib_obj_type == NIOS_MEMBER) and (proposed_object['create_token']): 331 proposed_object = None 332 # the function creates a token that can be used by a pre-provisioned member to join the grid 333 result['api_results'] = self.call_func('create_token', ref, proposed_object) 334 result['changed'] = True 335 elif modified: 336 if 'ipv4addrs' in proposed_object: 337 if ('add' not in proposed_object['ipv4addrs'][0]) and ('remove' not in proposed_object['ipv4addrs'][0]): 338 self.check_if_recordname_exists(obj_filter, ib_obj_ref, ib_obj_type, current_object, proposed_object) 339 340 if (ib_obj_type in (NIOS_HOST_RECORD, NIOS_NETWORK_VIEW, NIOS_DNS_VIEW)): 341 run_update = True 342 proposed_object = self.on_update(proposed_object, ib_spec) 343 if 'ipv4addrs' in proposed_object: 344 if ('add' or 'remove') in proposed_object['ipv4addrs'][0]: 345 run_update, proposed_object = self.check_if_add_remove_ip_arg_exists(proposed_object) 346 if run_update: 347 res = self.update_object(ref, proposed_object) 348 result['changed'] = True 349 else: 350 res = ref 351 if (ib_obj_type in (NIOS_A_RECORD, NIOS_AAAA_RECORD, NIOS_PTR_RECORD, NIOS_SRV_RECORD)): 352 # popping 'view' key as update of 'view' is not supported with respect to a:record/aaaa:record/srv:record/ptr:record 353 proposed_object = self.on_update(proposed_object, ib_spec) 354 del proposed_object['view'] 355 if not self.module.check_mode: 356 res = self.update_object(ref, proposed_object) 357 result['changed'] = True 358 elif 'network_view' in proposed_object: 359 proposed_object.pop('network_view') 360 result['changed'] = True 361 if not self.module.check_mode and res is None: 362 proposed_object = self.on_update(proposed_object, ib_spec) 363 self.update_object(ref, proposed_object) 364 result['changed'] = True 365 366 elif state == 'absent': 367 if ref is not None: 368 if 'ipv4addrs' in proposed_object: 369 if 'remove' in proposed_object['ipv4addrs'][0]: 370 self.check_if_add_remove_ip_arg_exists(proposed_object) 371 self.update_object(ref, proposed_object) 372 result['changed'] = True 373 elif not self.module.check_mode: 374 self.delete_object(ref) 375 result['changed'] = True 376 377 return result 378 379 def check_if_recordname_exists(self, obj_filter, ib_obj_ref, ib_obj_type, current_object, proposed_object): 380 ''' Send POST request if host record input name and retrieved ref name is same, 381 but input IP and retrieved IP is different''' 382 383 if 'name' in (obj_filter and ib_obj_ref[0]) and ib_obj_type == NIOS_HOST_RECORD: 384 obj_host_name = obj_filter['name'] 385 ref_host_name = ib_obj_ref[0]['name'] 386 if 'ipv4addrs' in (current_object and proposed_object): 387 current_ip_addr = current_object['ipv4addrs'][0]['ipv4addr'] 388 proposed_ip_addr = proposed_object['ipv4addrs'][0]['ipv4addr'] 389 elif 'ipv6addrs' in (current_object and proposed_object): 390 current_ip_addr = current_object['ipv6addrs'][0]['ipv6addr'] 391 proposed_ip_addr = proposed_object['ipv6addrs'][0]['ipv6addr'] 392 393 if obj_host_name == ref_host_name and current_ip_addr != proposed_ip_addr: 394 self.create_object(ib_obj_type, proposed_object) 395 396 def check_if_nios_next_ip_exists(self, proposed_object): 397 ''' Check if nios_next_ip argument is passed in ipaddr while creating 398 host record, if yes then format proposed object ipv4addrs and pass 399 func:nextavailableip and ipaddr range to create hostrecord with next 400 available ip in one call to avoid any race condition ''' 401 402 if 'ipv4addrs' in proposed_object: 403 if 'nios_next_ip' in proposed_object['ipv4addrs'][0]['ipv4addr']: 404 ip_range = check_type_dict(proposed_object['ipv4addrs'][0]['ipv4addr'])['nios_next_ip'] 405 proposed_object['ipv4addrs'][0]['ipv4addr'] = NIOS_NEXT_AVAILABLE_IP + ':' + ip_range 406 elif 'ipv4addr' in proposed_object: 407 if 'nios_next_ip' in proposed_object['ipv4addr']: 408 ip_range = check_type_dict(proposed_object['ipv4addr'])['nios_next_ip'] 409 proposed_object['ipv4addr'] = NIOS_NEXT_AVAILABLE_IP + ':' + ip_range 410 411 return proposed_object 412 413 def check_if_add_remove_ip_arg_exists(self, proposed_object): 414 ''' 415 This function shall check if add/remove param is set to true and 416 is passed in the args, then we will update the proposed dictionary 417 to add/remove IP to existing host_record, if the user passes false 418 param with the argument nothing shall be done. 419 :returns: True if param is changed based on add/remove, and also the 420 changed proposed_object. 421 ''' 422 update = False 423 if 'add' in proposed_object['ipv4addrs'][0]: 424 if proposed_object['ipv4addrs'][0]['add']: 425 proposed_object['ipv4addrs+'] = proposed_object['ipv4addrs'] 426 del proposed_object['ipv4addrs'] 427 del proposed_object['ipv4addrs+'][0]['add'] 428 update = True 429 else: 430 del proposed_object['ipv4addrs'][0]['add'] 431 elif 'remove' in proposed_object['ipv4addrs'][0]: 432 if proposed_object['ipv4addrs'][0]['remove']: 433 proposed_object['ipv4addrs-'] = proposed_object['ipv4addrs'] 434 del proposed_object['ipv4addrs'] 435 del proposed_object['ipv4addrs-'][0]['remove'] 436 update = True 437 else: 438 del proposed_object['ipv4addrs'][0]['remove'] 439 return update, proposed_object 440 441 def issubset(self, item, objects): 442 ''' Checks if item is a subset of objects 443 :args item: the subset item to validate 444 :args objects: superset list of objects to validate against 445 :returns: True if item is a subset of one entry in objects otherwise 446 this method will return None 447 ''' 448 for obj in objects: 449 if isinstance(item, dict): 450 if all(entry in obj.items() for entry in item.items()): 451 return True 452 else: 453 if item in obj: 454 return True 455 456 def compare_objects(self, current_object, proposed_object): 457 for key, proposed_item in iteritems(proposed_object): 458 current_item = current_object.get(key) 459 460 # if proposed has a key that current doesn't then the objects are 461 # not equal and False will be immediately returned 462 if current_item is None: 463 return False 464 465 elif isinstance(proposed_item, list): 466 if key == 'aliases': 467 if set(current_item) != set(proposed_item): 468 return False 469 for subitem in proposed_item: 470 if not self.issubset(subitem, current_item): 471 return False 472 473 elif isinstance(proposed_item, dict): 474 return self.compare_objects(current_item, proposed_item) 475 476 else: 477 if current_item != proposed_item: 478 return False 479 480 return True 481 482 def get_object_ref(self, module, ib_obj_type, obj_filter, ib_spec): 483 ''' this function gets the reference object of pre-existing nios objects ''' 484 485 update = False 486 old_name = new_name = None 487 if ('name' in obj_filter): 488 # gets and returns the current object based on name/old_name passed 489 try: 490 name_obj = check_type_dict(obj_filter['name']) 491 old_name = name_obj['old_name'] 492 new_name = name_obj['new_name'] 493 except TypeError: 494 name = obj_filter['name'] 495 496 if old_name and new_name: 497 if (ib_obj_type == NIOS_HOST_RECORD): 498 test_obj_filter = dict([('name', old_name), ('view', obj_filter['view'])]) 499 elif (ib_obj_type in (NIOS_AAAA_RECORD, NIOS_A_RECORD)): 500 test_obj_filter = obj_filter 501 else: 502 test_obj_filter = dict([('name', old_name)]) 503 # get the object reference 504 ib_obj = self.get_object(ib_obj_type, test_obj_filter, return_fields=list(ib_spec.keys())) 505 if ib_obj: 506 obj_filter['name'] = new_name 507 else: 508 test_obj_filter['name'] = new_name 509 ib_obj = self.get_object(ib_obj_type, test_obj_filter, return_fields=list(ib_spec.keys())) 510 update = True 511 return ib_obj, update, new_name 512 if (ib_obj_type == NIOS_HOST_RECORD): 513 # to check only by name if dns bypassing is set 514 if not obj_filter['configure_for_dns']: 515 test_obj_filter = dict([('name', name)]) 516 else: 517 test_obj_filter = dict([('name', name), ('view', obj_filter['view'])]) 518 elif (ib_obj_type == NIOS_IPV4_FIXED_ADDRESS or ib_obj_type == NIOS_IPV6_FIXED_ADDRESS and 'mac' in obj_filter): 519 test_obj_filter = dict([['mac', obj_filter['mac']]]) 520 elif (ib_obj_type == NIOS_A_RECORD): 521 # resolves issue where a_record with uppercase name was returning null and was failing 522 test_obj_filter = obj_filter 523 test_obj_filter['name'] = test_obj_filter['name'].lower() 524 # resolves issue where multiple a_records with same name and different IP address 525 try: 526 ipaddr_obj = check_type_dict(obj_filter['ipv4addr']) 527 ipaddr = ipaddr_obj['old_ipv4addr'] 528 except TypeError: 529 ipaddr = obj_filter['ipv4addr'] 530 test_obj_filter['ipv4addr'] = ipaddr 531 elif (ib_obj_type == NIOS_TXT_RECORD): 532 # resolves issue where multiple txt_records with same name and different text 533 test_obj_filter = obj_filter 534 try: 535 text_obj = check_type_dict(obj_filter['text']) 536 txt = text_obj['old_text'] 537 except TypeError: 538 txt = obj_filter['text'] 539 test_obj_filter['text'] = txt 540 # check if test_obj_filter is empty copy passed obj_filter 541 else: 542 test_obj_filter = obj_filter 543 ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=list(ib_spec.keys())) 544 elif (ib_obj_type == NIOS_A_RECORD): 545 # resolves issue where multiple a_records with same name and different IP address 546 test_obj_filter = obj_filter 547 try: 548 ipaddr_obj = check_type_dict(obj_filter['ipv4addr']) 549 ipaddr = ipaddr_obj['old_ipv4addr'] 550 except TypeError: 551 ipaddr = obj_filter['ipv4addr'] 552 test_obj_filter['ipv4addr'] = ipaddr 553 ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=list(ib_spec.keys())) 554 elif (ib_obj_type == NIOS_TXT_RECORD): 555 # resolves issue where multiple txt_records with same name and different text 556 test_obj_filter = obj_filter 557 try: 558 text_obj = check_type_dict(obj_filter['text']) 559 txt = text_obj['old_text'] 560 except TypeError: 561 txt = obj_filter['text'] 562 test_obj_filter['text'] = txt 563 ib_obj = self.get_object(ib_obj_type, test_obj_filter.copy(), return_fields=list(ib_spec.keys())) 564 elif (ib_obj_type == NIOS_ZONE): 565 # del key 'restart_if_needed' as nios_zone get_object fails with the key present 566 temp = ib_spec['restart_if_needed'] 567 del ib_spec['restart_if_needed'] 568 ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=list(ib_spec.keys())) 569 # reinstate restart_if_needed if ib_obj is none, meaning there's no existing nios_zone ref 570 if not ib_obj: 571 ib_spec['restart_if_needed'] = temp 572 elif (ib_obj_type == NIOS_MEMBER): 573 # del key 'create_token' as nios_member get_object fails with the key present 574 temp = ib_spec['create_token'] 575 del ib_spec['create_token'] 576 ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=list(ib_spec.keys())) 577 if temp: 578 # reinstate 'create_token' key 579 ib_spec['create_token'] = temp 580 else: 581 ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=list(ib_spec.keys())) 582 return ib_obj, update, new_name 583 584 def on_update(self, proposed_object, ib_spec): 585 ''' Event called before the update is sent to the API endpoing 586 This method will allow the final proposed object to be changed 587 and/or keys filtered before it is sent to the API endpoint to 588 be processed. 589 :args proposed_object: A dict item that will be encoded and sent 590 the API endpoint with the updated data structure 591 :returns: updated object to be sent to API endpoint 592 ''' 593 keys = set() 594 for key, value in iteritems(proposed_object): 595 update = ib_spec[key].get('update', True) 596 if not update: 597 keys.add(key) 598 return dict([(k, v) for k, v in iteritems(proposed_object) if k not in keys]) 599