class ModuleExecutor(object): def __init__(self, module): self.module = module self.fetcher = NitroAPIFetcher(self.module) self.main_nitro_class = 'sslcertkey' # Dictionary containing attribute information # for each NITRO object utilized by this module self.attribute_config = { 'sslcertkey': { 'attributes_list': [ 'certkey', 'cert', 'key', 'password', 'fipskey', 'hsmkey', 'inform', 'passplain', 'expirymonitor', 'notificationperiod', 'bundle', 'deletefromdevice', 'linkcertkeyname', 'nodomaincheck', 'ocspstaplingcache', ], 'transforms': { 'expirymonitor': lambda v: v.upper(), 'bundle': lambda v: 'YES' if v else 'NO', }, 'get_id_attributes': [ 'certkey', ], 'delete_id_attributes': [ 'certkey', 'deletefromdevice', ], 'non_updateable_attributes': [ ], }, } self.module_result = dict( changed=False, failed=False, loglines=loglines, ) self.change_keys = [ 'cert', 'key', 'fipskey', 'inform', ] self.update_keys = [ 'expirymonitor', 'notificationperiod', ] # Calculate functions will apply transforms to values read from playbook self.calculate_configured_ssl_certkey() def calculate_configured_ssl_certkey(self): log('ModuleExecutor.calculate_configured_ssl_certkey()') self.configured_ssl_certkey = {} for attribute in self.attribute_config['sslcertkey']['attributes_list']: value = self.module.params.get(attribute) # Skip null values if value is None: continue transform = self.attribute_config['sslcertkey']['transforms'].get(attribute) if transform is not None: value = transform(value) self.configured_ssl_certkey[attribute] = value log('calculated configured ssl certkey %s' % self.configured_ssl_certkey) def ssl_certkey_exists(self): log('ModuleExecutor.ssl_certkey_exists()') result = self.fetcher.get('sslcertkey', self.module.params['certkey']) log('get result %s' % result) if result['nitro_errorcode'] in [258, 1540]: return False elif result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) # Fallthrough return True def create_ssl_certkey(self): log('ModuleExecutor.create_ssl_certkey()') processed_data = copy.deepcopy(self.configured_ssl_certkey) # No domain check is flag for change operation if 'nodomaincheck' in processed_data: del processed_data['nodomaincheck'] # Flag for the delete operation if 'deletefromdevice' in processed_data: del processed_data['deletefromdevice'] post_data = { 'sslcertkey': processed_data } result = self.fetcher.post(post_data=post_data, resource='sslcertkey') log('post data: %s' % post_data) log('result of post: %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result['http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def _get_configured_for_identical_comparison(self): log('ModuleExecutor._get_configured_for_identical_comparison()') configured = {} skip_attributes = [ 'password', # Never returned from NITRO API 'passplain', # Never returned from NITRO API 'nodomaincheck', # Flag for change operation 'bundle', # Flag for create operation 'deletefromdevice', # Flag for the delete operation ] for attribute in self.configured_ssl_certkey: if attribute in skip_attributes: continue configured[attribute] = self.configured_ssl_certkey[attribute] log('Configured for comparison %s' % configured) return configured def ssl_certkey_identical(self): log('ModuleExecutor.ssl_certkey_identical()') result = self.fetcher.get('sslcertkey', self.configured_ssl_certkey['certkey']) self.retrieved_ssl_certkey = result['data']['sslcertkey'][0] # Keep track of what keys are different for update and change operations self.differing_keys = [] diff_list = [] for attribute in self._get_configured_for_identical_comparison(): retrieved_value = self.retrieved_ssl_certkey.get(attribute) configured_value = self.configured_ssl_certkey.get(attribute) if retrieved_value != configured_value: str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) self.differing_keys.append(attribute) diff_list.append('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) self.module_result['diff_list'] = diff_list if diff_list != []: return False else: return True def do_change_operation(self): log('ModuleExecutor.do_change_operation()') processed_data = copy.deepcopy(self.configured_ssl_certkey) # bundle is a flag for the create operation if 'bundle' in processed_data: del processed_data['bundle'] # Flag for the delete operation if 'deletefromdevice' in processed_data: del processed_data['deletefromdevice'] # Remove attributes that are used in the update operation for attribute in self.update_keys: if attribute in processed_data: del processed_data[attribute] post_data = { 'sslcertkey': processed_data } # Do change operation result = self.fetcher.post( post_data=post_data, resource='sslcertkey', action='update', ) log('post data: %s' % post_data) log('result of post: %s' % result) if result['http_response_data']['status'] == 200: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result['http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def do_update_operation(self): log('ModuleExecutor.do_update_operation()') processed_data = {} processed_data['certkey'] = self.configured_ssl_certkey['certkey'] for attribute in self.update_keys: if attribute in self.configured_ssl_certkey: processed_data[attribute] = self.configured_ssl_certkey[attribute] put_data = { 'sslcertkey': processed_data } result = self.fetcher.put(put_data=put_data, resource='sslcertkey') log('put data %s' % put_data) log('result of put %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def update_ssl_certkey(self): log('ModuleExecutor.update_ssl_certkey()') changed_keys = list(frozenset(self.differing_keys) & frozenset(self.change_keys)) if len(changed_keys) > 0: log('Keys that force change operation %s' % changed_keys) self.do_change_operation() updated_keys = list(frozenset(self.differing_keys) & frozenset(self.update_keys)) if len(updated_keys) > 0: log('Keys that force update operations %s' % updated_keys) self.do_update_operation() def update_or_create(self): log('ModuleExecutor.update_or_create()') if not self.ssl_certkey_exists(): self.module_result['changed'] = True if not self.module.check_mode: log('ssl certkey does not exist. Will create.') self.create_ssl_certkey() elif not self.ssl_certkey_identical(): self.module_result['changed'] = True if not self.module.check_mode: log('ssl certkey not identical. Will update.') self.update_ssl_certkey() else: self.module_result['changed'] = False def delete_ssl_certkey(self): log('ModuleExecutor.delete_ssl_certkey()') args = {} # Add delete flag if defined if 'deletefromdevice' in self.configured_ssl_certkey: if self.configured_ssl_certkey['deletefromdevice']: args['deletefromdevice'] = 'true' else: args['deletefromdevice'] = 'false' result = self.fetcher.delete( resource='sslcertkey', id=self.configured_ssl_certkey['certkey'], args=args, ) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def delete(self): log('ModuleExecutor.delete()') if self.ssl_certkey_exists(): self.module_result['changed'] = True if not self.module.check_mode: self.delete_ssl_certkey() def main(self): try: if self.module.params['state'] == 'present': self.update_or_create() elif self.module.params['state'] == 'absent': self.delete() self.module.exit_json(**self.module_result) except NitroException as e: msg = "nitro exception errorcode=%s, message=%s, severity=%s" % (str(e.errorcode), e.message, e.severity) self.module.fail_json(msg=msg, **self.module_result) except Exception as e: msg = 'Exception %s: %s' % (type(e), str(e)) self.module.fail_json(msg=msg, **self.module_result)
class ModuleExecutor(object): def __init__(self, module): self.module = module self.fetcher = NitroAPIFetcher(self.module) # Dictionary containing attribute information # for each NITRO object utilized by this module self.attribute_config = { 'nspartition': { 'attributes_list': [ 'partitionname', 'maxbandwidth', 'minbandwidth', 'maxconn', 'maxmemlimit', 'partitionmac', ], 'transforms': { }, 'get_id_attributes': [ 'partitionname', ], 'delete_id_attributes': [ 'partitionname', ], 'non_updateable_attributes': [ ], }, } self.module_result = dict( changed=False, failed=False, loglines=loglines, ) # Calculate functions will apply transforms to values read from playbook self.calculate_configured_nspartition() def calculate_configured_nspartition(self): log('ModuleExecutor.calculate_configured_nspartition()') self.configured_nspartition = {} for attribute in self.attribute_config['nspartition']['attributes_list']: value = self.module.params.get(attribute) # Skip null values if value is None: continue transform = self.attribute_config['nspartition']['transforms'].get(attribute) if transform is not None: value = transform(value) self.configured_nspartition[attribute] = value log('calculated configured nspartition %s' % self.configured_nspartition) def nspartition_exists(self): log('ModuleExecutor.nspartition_exists()') result = self.fetcher.get('nspartition', id=self.module.params['partitionname']) log('get result %s' % result) # nspartition does not exist if result['nitro_errorcode'] == 2755: return False elif result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) # Fallthrough # Save retrieved nspartition contents for nspartition_identical() self.retrieved_nspartition = result['data']['nspartition'][0] return True def create_nspartition(self): log('ModuleExecutor.create_nspartition()') post_data = copy.deepcopy(self.configured_nspartition) post_data = { 'nspartition': post_data } result = self.fetcher.post(post_data=post_data, resource='nspartition') log('post data: %s' % post_data) log('result of post: %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result['http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def update_nspartition(self): log('ModuleExecutor.update_nspartition()') # Catching trying to change non updateable attributes is done in self.nspartition_identical() put_payload = copy.deepcopy(self.configured_nspartition) for attribute in self.attribute_config['nspartition']['non_updateable_attributes']: if attribute in put_payload: del put_payload[attribute] put_data = { 'nspartition': put_payload } log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource='nspartition') log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def nspartition_identical(self): log('ModuleExecutor.nspartition_identical()') diff_list = [] non_updateable_list = [] for attribute in self.configured_nspartition.keys(): retrieved_value = self.retrieved_nspartition.get(attribute) configured_value = self.configured_nspartition.get(attribute) if retrieved_value != configured_value: str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) diff_list.append('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) entry = 'Attribute "%s" differs. Playbook parameter: "%s". Retrieved NITRO object: "%s"' % (attribute, configured_value, retrieved_value) # Also append changed values to the non updateable list if attribute in self.attribute_config['nspartition']['non_updateable_attributes']: non_updateable_list.append(attribute) self.module_result['diff_list'] = diff_list if non_updateable_list != []: msg = 'Cannot change value for the following non updateable attributes %s' % non_updateable_list self.module.fail_json(msg=msg, **self.module_result) if diff_list != []: return False else: return True def update_or_create(self): log('ModuleExecutor.update_or_create()') # Create or update main object if not self.nspartition_exists(): self.module_result['changed'] = True if not self.module.check_mode: log('nspartition does not exist. Will create.') self.create_nspartition() else: if not self.nspartition_identical(): log('Existing nspartition does not have identical values to configured. Will update.') self.module_result['changed'] = True if not self.module.check_mode: self.update_nspartition() else: log('Existing nspartition has identical values to configured.') def delete_nspartition(self): log('ModuleExecutor.delete_nspartition()') result = self.fetcher.delete( resource='nspartition', id=self.module.params['partitionname'], ) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def delete(self): log('ModuleExecutor.delete()') if self.nspartition_exists(): self.module_result['changed'] = True if not self.module.check_mode: self.delete_nspartition() def switch_partition(self): log('ModuleExecutor.switch_partition') post_data = { 'nspartition':{ 'partitionname': self.module.params['partitionname'], } } result = self.fetcher.post(post_data=post_data, resource='nspartition', action='Switch') if result['http_response_data']['status'] != 200: msg = 'Switch partition operation failed' self.module.fail_json(msg=msg, **self.module_result) def main(self): try: if self.module.params['state'] == 'present': self.update_or_create() if self.module.params.get('switch_partition', False): self.switch_partition() elif self.module.params['state'] == 'absent': self.delete() self.module.exit_json(**self.module_result) except NitroException as e: msg = "nitro exception errorcode=%s, message=%s, severity=%s" % (str(e.errorcode), e.message, e.severity) self.module.fail_json(msg=msg, **self.module_result) except Exception as e: msg = 'Exception %s: %s' % (type(e), str(e)) self.module.fail_json(msg=msg, **self.module_result)
class ModuleExecutor(object): def __init__(self, module): self.module = module self.fetcher = NitroAPIFetcher(self.module) self.main_nitro_class = 'service' # Dictionary containing attribute information # for each NITRO object utilized by this module self.attribute_config = { 'service': { 'attributes_list': [ 'name', 'ip', 'servername', 'servicetype', 'port', 'cleartextport', 'cachetype', 'maxclient', 'healthmonitor', 'maxreq', 'cacheable', 'cip', 'cipheader', 'usip', 'pathmonitor', 'pathmonitorindv', 'useproxyport', 'sc', 'sp', 'rtspsessionidremap', 'clttimeout', 'svrtimeout', 'customserverid', 'serverid', 'cka', 'tcpb', 'cmp', 'maxbandwidth', 'accessdown', 'monthreshold', 'downstateflush', 'tcpprofilename', 'httpprofilename', 'contentinspectionprofilename', 'hashid', 'comment', 'appflowlog', 'netprofile', 'td', 'processlocal', 'dnsprofilename', 'monconnectionclose', 'ipaddress', 'weight', 'monitor_name_svc', 'riseapbrstatsmsgcode', 'delay', 'graceful', 'all', 'Internal', ], 'transforms': { 'healthmonitor': lambda v: 'YES' if v else 'NO', 'cacheable': lambda v: 'YES' if v else 'NO', 'cip': lambda v: v.upper(), 'usip': lambda v: 'YES' if v else 'NO', 'pathmonitor': lambda v: 'YES' if v else 'NO', 'pathmonitorindv': lambda v: 'YES' if v else 'NO', 'useproxyport': lambda v: 'YES' if v else 'NO', 'sc': lambda v: 'ON' if v else 'OFF', 'sp': lambda v: 'ON' if v else 'OFF', 'rtspsessionidremap': lambda v: 'ON' if v else 'OFF', 'cka': lambda v: 'YES' if v else 'NO', 'tcpb': lambda v: 'YES' if v else 'NO', 'cmp': lambda v: 'YES' if v else 'NO', 'accessdown': lambda v: 'YES' if v else 'NO', 'downstateflush': lambda v: v.upper(), 'appflowlog': lambda v: v.upper(), 'processlocal': lambda v: v.upper(), 'graceful': lambda v: 'YES' if v else 'NO', }, 'get_id_attributes': [ 'name', ], 'delete_id_attributes': [ 'name', ], 'non_updateable_attributes': [ 'ip', 'servername', 'servicetype', 'port', 'cleartextport', 'cachetype', 'state', 'td', 'riseapbrstatsmsgcode', 'delay', 'graceful', 'all', 'Internal', 'newname', ], }, 'monitor_bindings': { 'attributes_list': [ 'monitor_name', 'monstate', 'weight', 'passive', ], 'transforms': { 'monstate': lambda v: v.upper(), 'weight': str, }, 'get_id_attributes': [ 'name', ], 'delete_id_attributes': [ 'monitor_name', 'name', ] } } self.module_result = dict( changed=False, failed=False, loglines=loglines, ) self.prepared_list = [] self.calculate_configured_service() self.calculate_configured_monitor_bindings() def calculate_configured_service(self): log('ModuleExecutor.calculate_configured_service()') self.configured_service = {} for attribute in self.attribute_config['service']['attributes_list']: value = self.module.params.get(attribute) # Skip null values if value is None: continue transform = self.attribute_config['service']['transforms'].get(attribute) if transform is not None: value = transform(value) self.configured_service[attribute] = value log('calculated configured service%s' % self.configured_service) def calculate_configured_monitor_bindings(self): log('ModuleExecutor.calculate_configured_monitor_bindings()') self.configured_monitor_bindings = [] if self.module.params.get('monitor_bindings') is None: return for monitor_binding in self.module.params['monitor_bindings']: member = {} member['name'] = self.module.params['name'] for attribute in self.attribute_config['monitor_bindings']['attributes_list']: # Disregard null values value = monitor_binding.get(attribute) if value is None: continue transform = self.attribute_config['monitor_bindings']['transforms'].get(attribute) if transform is not None: value = transform(value) member[attribute] = value self.configured_monitor_bindings.append(member) log('calculated configured monitor bindings %s' % self.configured_monitor_bindings) def service_exists(self): log('ModuleExecutor.service_exists()') result = self.fetcher.get('service', self.module.params['name']) log('get result %s' % result) if result['nitro_errorcode'] == 0: return True elif result['nitro_errorcode'] == 344: return False else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def create_service(self): log('ModuleExecutor.create_service()') post_data = copy.deepcopy(self.configured_service) # Need to copy ipaddress to the ip attribute just for the create function if 'ip' not in post_data and 'ipaddress' in post_data: post_data['ip'] = post_data['ipaddress'] post_data = { 'service': post_data } result = self.fetcher.post(post_data=post_data, resource='service') log('post data: %s' % post_data) log('result of post: %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result['http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def update_service(self): log('ModuleExecutor.update_service()') # Catching trying to change non updateable attributes is done in self.service_identical() put_payload = copy.deepcopy(self.configured_service) for attribute in self.attribute_config['service']['non_updateable_attributes']: if attribute in put_payload: del put_payload[attribute] # Check that non updateable values have not changed put_data = { 'service': put_payload } log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource='service') log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def service_identical(self): log('ModuleExecutor.service_identical()') result = self.fetcher.get('service', self.module.params['name']) retrieved_object = result['data']['service'][0] if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) diff_list = [] non_updateable_list = [] for attribute in self.configured_service.keys(): retrieved_value = retrieved_object.get(attribute) configured_value = self.configured_service.get(attribute) if retrieved_value != configured_value: str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) diff_list.append('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) entry = 'Attribute "%s" differs. Playbook parameter: "%s". Retrieved NITRO object: "%s"' % (attribute, configured_value, retrieved_value) self.prepared_list.append(entry) # Also append changed values to the non updateable list if attribute in self.attribute_config['service']['non_updateable_attributes']: non_updateable_list.append(attribute) self.module_result['diff_list'] = diff_list if non_updateable_list != []: msg = 'Cannot change value for the following non updateable attributes %s' % non_updateable_list self.module.fail_json(msg=msg, **self.module_result) if diff_list != []: return False else: return True def update_or_create(self): log('ModuleExecutor.update_or_create()') # Create or update main object if not self.service_exists(): self.module_result['changed'] = True self.prepared_list.append('Create service') if not self.module.check_mode: log('Service does not exist. Will create.') self.create_service() else: if not self.service_identical(): log('Existing service does not have identical values to configured. Will update.') self.module_result['changed'] = True if not self.module.check_mode: self.update_service() else: log('Existing service has identical values to configured.') self.sync_bindings() def delete_service(self): result = self.fetcher.delete(resource='service', id=self.module.params['name']) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def delete(self): log('ModuleExecutor.delete()') if self.service_exists(): self.module_result['changed'] = True self.prepared_list.append('Delete service') if not self.module.check_mode: self.delete_service() def _get_transformed_dict(self, transforms, values_dict): actual_values_dict = {} for key in values_dict: value = values_dict.get(key) transform = transforms.get(key) if transform is not None: value = transform(values_dict.get(key)) actual_values_dict[key] = value return actual_values_dict def get_existing_monitor_bindings(self): log('ModuleExecutor.get_existing_monitor_bindings()') result = self.fetcher.get('service_lbmonitor_binding', self.module.params['name']) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 'service_lbmonitor_binding' in result['data']: return result['data']['service_lbmonitor_binding'] else: return [] def add_monitor_binding(self, configured_dict): log('ModuleExecutor.add_monitor_binding()') put_values = copy.deepcopy(configured_dict) put_values['name'] = self.configured_service['name'] put_values = self._get_transformed_dict( transforms=self.attribute_config['monitor_bindings']['transforms'], values_dict=put_values ) put_data = {'service_lbmonitor_binding': put_values} log('put data %s' % put_data) result = self.fetcher.put( put_data=put_data, resource='service_lbmonitor_binding', id=self.configured_service['name'], ) log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def delete_monitor_binding(self, configured_dict): log('ModuleExecutor.delete_monitor_binding()') monitor_binding = copy.deepcopy(configured_dict) args = {} for attribute in self.attribute_config['monitor_bindings']['delete_id_attributes']: value = monitor_binding.get(attribute) if value is not None: args[attribute] = value result = self.fetcher.delete( resource='service_lbmonitor_binding', id=self.configured_service['name'], args=args ) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def monitor_binding_identical(self, configured, retrieved): log('ModuleExecutor.monitor_binding_identical()') ret_val = True for key in configured.keys(): configured_value = configured.get(key) retrieved_value = retrieved.get(key) if configured_value != retrieved_value: str_tuple = ( key, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) log('Monitor binding attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) ret_val = False return ret_val def sync_monitor_bindings(self): log('ModuleExecutor.sync_monitor_bindings()') try: existing_monitor_bindings = self.get_existing_monitor_bindings() except NitroException as e: if e.errorcode == 344: # Set this to empty for correct diff in check mode existing_monitor_bindings = [] else: raise log('existing_monitor_bindings %s' % existing_monitor_bindings) # Exclude the ignored monitors filtered_monitor_bindings = [] for monitor in existing_monitor_bindings: if monitor['monitor_name'] in self.module.params.get('ignore_monitors', []): continue filtered_monitor_bindings.append(monitor) log('filtered_monitor_bindings %s' % filtered_monitor_bindings) # First get the existing bindings configured_already_present = [] # Delete any binding that is not exactly as the configured for existing_monitor_binding in filtered_monitor_bindings: for configured_monitor_binding in self.configured_monitor_bindings: if self.monitor_binding_identical(configured_monitor_binding, existing_monitor_binding): configured_already_present.append(configured_monitor_binding) break else: log('Will delete binding') self.module_result['changed'] = True self.prepared_list.append('Delete monitor_binding: %s' % self.reduced_binding_dict(existing_monitor_binding)) if not self.module.check_mode: self.delete_monitor_binding(existing_monitor_binding) # Create the bindings objects that we marked in previous loop log('configured_already_present %s' % configured_already_present) for configured_monitor_binding in self.configured_monitor_bindings: if configured_monitor_binding in configured_already_present: log('Configured binding already exists') continue else: log('Configured binding does not already exist') self.module_result['changed'] = True self.prepared_list.append('Add monitor_binding: %s' % self.reduced_binding_dict(configured_monitor_binding)) if not self.module.check_mode: self.add_monitor_binding(configured_monitor_binding) def reduced_binding_dict(self, monitor_binding): reduced_dict = {} for key in monitor_binding: if key in self.attribute_config['monitor_bindings']['attributes_list']: reduced_dict[key] = monitor_binding[key] return reduced_dict def sync_bindings(self): log('ModuleExecutor.sync_bindings()') self.sync_monitor_bindings() def do_state_change(self): log('ModuleExecutor.do_state_change()') if self.module.check_mode: return # Fallthrough operation_attributes = [ 'graceful', 'delay', ] post_data = { 'service': { 'name': self.configured_service['name'], } } for attribute in operation_attributes: value = self.configured_service.get(attribute) if value is not None: post_data['service'][attribute] = value disabled = self.module.params['disabled'] args = {} if disabled: action = 'disable' else: action = 'enable' log('disable/enable post data %s' % post_data) result = self.fetcher.post(post_data=post_data, resource='service', action=action) log('result of post %s' % result) if result['http_response_data']['status'] != 200: msg = 'Disable/Enable operation failed' self.module.fail_json(msg=msg, **self.module_result) def main(self): try: if self.module.params['state'] == 'present': self.update_or_create() self.do_state_change() elif self.module.params['state'] == 'absent': self.delete() if self.module._diff: self.module_result['diff'] = {'prepared': '\n'.join(self.prepared_list)} self.module.exit_json(**self.module_result) except NitroException as e: msg = "nitro exception errorcode=%s, message=%s, severity=%s" % (str(e.errorcode), e.message, e.severity) self.module.fail_json(msg=msg, **self.module_result) except Exception as e: msg = 'Exception %s: %s' % (type(e), str(e)) self.module.fail_json(msg=msg, **self.module_result)
class ModuleExecutor(object): def __init__(self, module): self.module = module self.fetcher = NitroAPIFetcher(self.module) # Dictionary containing attribute information # for each NITRO object utilized by this module self.attribute_config = { 'nsip': { 'attributes_list': [ 'ipaddress', 'netmask', 'type', 'arp', 'icmp', 'vserver', 'telnet', 'ftp', 'gui', 'ssh', 'snmp', 'mgmtaccess', 'restrictaccess', 'dynamicrouting', 'decrementttl', 'ospf', 'bgp', 'rip', 'hostroute', 'advertiseondefaultpartition', 'networkroute', 'tag', 'hostrtgw', 'metric', 'vserverrhilevel', 'vserverrhimode', 'ospflsatype', 'ospfarea', 'vrid', 'icmpresponse', 'ownernode', 'arpresponse', 'ownerdownresponse', 'td', 'arpowner', ], 'transforms': { 'arp': lambda v: v.upper(), 'icmp': lambda v: v.upper(), 'vserver': lambda v: v.upper(), 'telnet': lambda v: v.upper(), 'ftp': lambda v: v.upper(), 'ssh': lambda v: v.upper(), 'snmp': lambda v: v.upper(), 'mgmtaccess': lambda v: v.upper(), 'restrictaccess': lambda v: v.upper(), 'dynamicrouting': lambda v: v.upper(), 'decrementttl': lambda v: v.upper(), 'ospf': lambda v: v.upper(), 'bgp': lambda v: v.upper(), 'rip': lambda v: v.upper(), 'hostroute': lambda v: v.upper(), 'advertiseondefaultpartition': lambda v: v.upper(), 'networkroute': lambda v: v.upper(), 'ownerdownresponse': lambda v: 'YES' if v else 'NO', }, 'get_id_attributes': [ ], 'delete_id_attributes': [ 'ipaddress', 'td', ], 'non_updateable_attributes': [ 'type', 'state', 'ownernode', ], }, } self.module_result = dict( changed=False, failed=False, loglines=loglines, ) # Calculate functions will apply transforms to values read from playbook self.calculate_configured_nsip() def calculate_configured_nsip(self): log('ModuleExecutor.calculate_configured_nsip()') self.configured_nsip = {} for attribute in self.attribute_config['nsip']['attributes_list']: value = self.module.params.get(attribute) # Skip null values if value is None: continue transform = self.attribute_config['nsip']['transforms'].get(attribute) if transform is not None: value = transform(value) self.configured_nsip[attribute] = value log('calculated configured nsip %s' % self.configured_nsip) def nsip_exists(self): log('ModuleExecutor.nsip_exists()') args = {} args['ipaddress'] = self.module.params['ipaddress'] result = self.fetcher.get('nsip', args=args) log('get result %s' % result) # NSIP does not exist if result['nitro_errorcode'] == 258: return False elif result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) # Fallthrough # Save retrieved file contents for nsip_identical() self.retrieved_nsip = result['data']['nsip'][0] return True def create_nsip(self): log('ModuleExecutor.create_nsip()') post_data = copy.deepcopy(self.configured_nsip) post_data = { 'nsip': post_data } result = self.fetcher.post(post_data=post_data, resource='nsip') log('post data: %s' % post_data) log('result of post: %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result['http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def update_nsip(self): log('ModuleExecutor.update_nsip()') # Catching trying to change non updateable attributes is done in self.nsip_identical() put_payload = copy.deepcopy(self.configured_nsip) for attribute in self.attribute_config['nsip']['non_updateable_attributes']: if attribute in put_payload: del put_payload[attribute] put_data = { 'nsip': put_payload } log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource='nsip') log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def nsip_identical(self): log('ModuleExecutor.nsip_identical()') diff_list = [] non_updateable_list = [] for attribute in self.configured_nsip.keys(): retrieved_value = self.retrieved_nsip.get(attribute) configured_value = self.configured_nsip.get(attribute) if retrieved_value != configured_value: str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) diff_list.append('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) entry = 'Attribute "%s" differs. Playbook parameter: "%s". Retrieved NITRO object: "%s"' % (attribute, configured_value, retrieved_value) # Also append changed values to the non updateable list if attribute in self.attribute_config['nsip']['non_updateable_attributes']: non_updateable_list.append(attribute) self.module_result['diff_list'] = diff_list if non_updateable_list != []: msg = 'Cannot change value for the following non updateable attributes %s' % non_updateable_list self.module.fail_json(msg=msg, **self.module_result) if diff_list != []: return False else: return True def update_or_create(self): log('ModuleExecutor.update_or_create()') # Create or update main object if not self.nsip_exists(): self.module_result['changed'] = True if not self.module.check_mode: log('nsip does not exist. Will create.') self.create_nsip() else: if not self.nsip_identical(): log('Existing nsip does not have identical values to configured. Will update.') self.module_result['changed'] = True if not self.module.check_mode: self.update_nsip() else: log('Existing nsip has identical values to configured.') def delete_nsip(self): log('ModuleExecutor.delete_nsip()') args = {} td = self.configured_nsip.get('td') if td is not None: args['td'] = td result = self.fetcher.delete( resource='nsip', id=self.module.params['ipaddress'], args=args, ) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def delete(self): log('ModuleExecutor.delete()') if self.nsip_exists(): self.module_result['changed'] = True if not self.module.check_mode: self.delete_nsip() def do_state_change(self): log('ModuleExecutor.do_state_change()') if self.module.check_mode: return # Fallthrough post_data = { 'nsip': { 'ipaddress': self.configured_nsip['ipaddress'], } } # Append td if defined td = self.configured_nsip.get('td') if td is not None: post_data['nsip']['td'] = td disabled = self.module.params.get('disabled') # Do not operate if disabled is not defined if disabled is None: return # Fallthrough if disabled: action = 'disable' else: action = 'enable' log('disable/enable post data %s' % post_data) result = self.fetcher.post(post_data=post_data, resource='nsip', action=action) log('result of post %s' % result) if result['http_response_data']['status'] != 200: msg = 'Disable/Enable operation failed' self.module.fail_json(msg=msg, **self.module_result) def main(self): try: if self.module.params['state'] == 'present': self.update_or_create() self.do_state_change() elif self.module.params['state'] == 'absent': self.delete() self.module.exit_json(**self.module_result) except NitroException as e: msg = "nitro exception errorcode=%s, message=%s, severity=%s" % (str(e.errorcode), e.message, e.severity) self.module.fail_json(msg=msg, **self.module_result) except Exception as e: msg = 'Exception %s: %s' % (type(e), str(e)) self.module.fail_json(msg=msg, **self.module_result)
class ModuleExecutor(object): def __init__(self, module): self.module = module self.fetcher = NitroAPIFetcher(self.module) self.module_result = dict( changed=False, failed=False, loglines=loglines, ) self.lifecycle = self.module.params['workflow']['lifecycle'] self.retrieved_object = None self.configured_object = self.module.params['resource'] self.endpoint = self.module.params['workflow'].get('endpoint') self.differing_attributes = [] # Parse non updateable attributes self.non_updateable_attributes = self.module.params['workflow'].get( 'non_updateable_attributes') if self.non_updateable_attributes is None: self.non_updateable_attributes = [] id_key = self.module.params['workflow'].get('primary_id_attribute') if id_key is not None: self.id = self.module.params['resource'][id_key] else: self.id = None log('self.id %s' % self.id) # Parse delete id attributes self.delete_id_attributes = self.module.params['workflow'].get( 'delete_id_attributes') if self.delete_id_attributes is None: self.delete_id_attributes = [] self.prepared_list = [] def resource_exists(self): log('ModuleExecutor.resource_exists()') if self.lifecycle == 'object': return self.object_exists() elif self.lifecycle == 'binding': return self.binding_exists() elif self.lifecycle == 'bindings_list': return self.bindings_list_exists() elif self.lifecycle == 'non_updateable_object': return self.non_updateable_object_exists() elif self.lifecycle == 'object_by_args': return self.object_by_args_exists() elif self.lifecycle == 'parameter_object': return self.parameter_object_exists() else: msg = 'Unrecognized lifecycle value "%s"' % self.lifecycle self.module.fail_json(msg=msg, **self.module_result) def resource_identical(self): log('ModuleExecutor.resource_identical()') if self.lifecycle == 'object': return self.object_identical() elif self.lifecycle == 'binding': return self.binding_identical() elif self.lifecycle == 'bindings_list': return self.bindings_list_identical() elif self.lifecycle == 'non_updateable_object': return self.non_updateable_object_identical() elif self.lifecycle == 'object_by_args': return self.object_by_args_identical() elif self.lifecycle == 'parameter_object': return self.parameter_object_identical() def resource_create(self): log('ModuleExecutor.resource_create()') if self.lifecycle == 'object': self.object_create() elif self.lifecycle == 'binding': self.binding_create() elif self.lifecycle == 'bindings_list': self.bindings_list_create() elif self.lifecycle == 'non_updateable_object': return self.non_updateable_object_create() elif self.lifecycle == 'object_by_args': self.object_by_args_create() elif self.lifecycle == 'parameter_object': self.parameter_object_create() def resource_update(self): log('ModuleExecutor.resource_update()') if self.lifecycle == 'object': self.object_update() elif self.lifecycle == 'binding': self.binding_update() elif self.lifecycle == 'bindings_list': self.bindings_list_update() elif self.lifecycle == 'non_updateable_object': return self.non_updateable_object_update() elif self.lifecycle == 'object_by_args': self.object_by_args_update() elif self.lifecycle == 'parameter_object': self.parameter_object_update() def resource_delete(self): log('ModuleExecutor.resource_delete()') if self.lifecycle == 'object': self.object_delete() elif self.lifecycle == 'binding': self.binding_delete() elif self.lifecycle == 'bindings_list': self.bindings_list_delete() elif self.lifecycle == 'non_updateable_object': return self.non_updateable_object_delete() elif self.lifecycle == 'object_by_args': self.object_by_args_delete() elif self.lifecycle == 'parameter_object': self.parameter_object_delete() def binding_matches_id_attributes(self, binding): log('ModuleExecutor.binding_matches_id_attributes()') retval = True id_keys = [] id_keys.append(self.module.params['workflow']['primary_id_attribute']) id_keys.extend(self.module.params['workflow']['delete_id_attributes']) for attribute in self.module.params['resource'].keys(): if attribute in id_keys: configured_value = self.module.params['resource'][attribute] retrieved_value = binding.get(attribute) if configured_value != retrieved_value: log('Non matching id attribute %s' % attribute) retval = False return retval def binding_exists(self): log('ModuleExecutor.binding_exists()') result = self.fetcher.get(self.endpoint, self.id) log('get result %s' % result) if result['nitro_errorcode'] == 0: if self.endpoint not in result['data']: return False objects_returned = result['data'][self.endpoint] matching_objects = [] # Compare the present id attributes for object in objects_returned: if self.binding_matches_id_attributes(object): matching_objects.append(object) if len(matching_objects) == 0: return False elif len(matching_objects) == 1: self.retrieved_object = matching_objects[0] return True elif len(matching_objects) > 1: msg = 'Found multiple matching objects for binding' self.module.fail_json(msg=msg, **self.module_result) elif result['nitro_errorcode'] == self.module.params['workflow'][ 'bound_resource_missing_errorcode']: return False else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def binding_identical(self): log('ModuleExecutor.binding_identical()') return self.object_identical() def binding_create(self): log('ModuleExecutor.binding_create()') attributes = self.module.params['resource'] put_data = {self.endpoint: attributes} log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource=self.endpoint) log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def binding_update(self): log('ModuleExecutor.binding_update()') self.binding_delete() self.binding_create() def binding_delete(self): log('ModuleExecutor.binding_delete()') args = {} for key in self.module.params['workflow']['delete_id_attributes']: if key in self.configured_object: # Bool args values need to be lower case otherwise NITRO errors if isinstance(self.configured_object[key], bool): if self.configured_object[key]: args_value = 'true' else: args_value = 'false' else: args_value = self.configured_object[key] args[key] = args_value result = self.fetcher.delete(resource=self.endpoint, id=self.id, args=args) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def bindings_list_exists(self): log('ModuleExecutor.bindings_list_exists()') return self.bindings_list_identical() def bindings_list_identical(self): log('ModuleExecutor.bindings_list_identical()') configured_bindings = self.configured_object['bindings_list'] self.key_attributes = copy.deepcopy( self.module.params['workflow']['binding_workflow'] ['delete_id_attributes']) self.key_attributes.insert( 0, self.module.params['workflow']['binding_workflow'] ['primary_id_attribute']) # Sanity check that at least one item is defined in bindings_list if len(configured_bindings) == 0: msg = 'Bindings list must have at least one item.' self.module.fail_json(msg=msg, **self.module_result) # Fallthrough # Sanity check that all bindings have uniform resource attribute keys key_tuples = [] for binding in configured_bindings: attribute_keys_present = list( frozenset(binding.keys()) & frozenset(self.key_attributes)) key_tuple = tuple(sorted(attribute_keys_present)) key_tuples.append(key_tuple) key_tuple_set = frozenset(key_tuples) log('key_tuple_set %s' % key_tuple_set) if len(key_tuple_set) > 1: key_tuples = list(key_tuple_set) msg = 'Bindings list key attributes are not uniform. Attribute key sets found %s' % key_tuples self.module.fail_json(msg=msg, **self.module_result) # Fallthrough # Sanity check that all primary ids are one and the same primary_id_key = self.module.params['workflow']['binding_workflow'][ 'primary_id_attribute'] primary_ids_list = [ item[primary_id_key] for item in configured_bindings ] primary_ids_set = frozenset(primary_ids_list) log('primary_ids_set %s' % primary_ids_set) if len(primary_ids_set) > 1: keys = list(primary_ids_set) msg = 'Need to have only one primary id value. Found: %s' % keys self.module.fail_json(msg=msg, **self.module_result) # Fallthrough # Get existing bindings self.id = list(primary_ids_set)[0] self.endpoint = self.module.params['workflow']['binding_workflow'][ 'endpoint'] result = self.fetcher.get(self.endpoint, self.id) log('get result %s' % result) existing_bindings = [] if result['nitro_errorcode'] == 0: if self.endpoint not in result['data']: existing_bindings = [] else: existing_bindings = result['data'][self.endpoint] elif result['nitro_errorcode'] == self.module.params['workflow'][ 'binding_workflow']['bound_resource_missing_errorcode']: existing_bindings = [] else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) # Construct the dictionaries keyed by tuple of key attributes # First attribute must be the primary id attribute self.key_attributes_present = [] for item in self.key_attributes: if item in list(key_tuple_set)[0]: self.key_attributes_present.append(item) self.configured_bindings_dict = {} for binding in configured_bindings: binding_key = self._get_binding_key_tuple(binding) if binding_key in self.configured_bindings_dict: msg = 'Found duplicate key for configured bindings %s' % ( binding_key, ) self.module.fail_json(msg=msg, **self.module_result) log('Configured binding id %s registered to dict' % (binding_key, )) self.configured_bindings_dict[binding_key] = binding self.existing_bindings_dict = {} for binding in existing_bindings: binding_key = self._get_binding_key_tuple(binding) if binding_key in self.existing_bindings_dict: msg = 'Found duplicate key for existing bindings %s' % ( binding_key, ) self.module.fail_json(msg=msg, **self.module_result) log('Existing binding id %s registered to dict' % (binding_key, )) self.existing_bindings_dict[binding_key] = binding # Calculate to delete keys self.to_delete_keys = [] for existing_key in self.existing_bindings_dict: if existing_key not in self.configured_bindings_dict: log('Existing binding key marked for delete %s' % (existing_key, )) self.to_delete_keys.append(existing_key) # Calculate to update keys self.to_update_keys = [] for existing_key in self.existing_bindings_dict: if existing_key in self.configured_bindings_dict: configured = self.configured_bindings_dict[existing_key] existing = self.existing_bindings_dict[existing_key] if not self._binding_list_item_identical_to_configured( configured, existing): log('Existing binding key marked for update %s' % (existing_key, )) self.to_update_keys.append(existing_key) # Calculate to create keys self.to_create_keys = [] for configured_key in self.configured_bindings_dict: if configured_key not in self.existing_bindings_dict: log('Configured binding key marked for create %s' % (configured_key, )) self.to_create_keys.append(configured_key) # Calculate all changes all_change_keys = self.to_create_keys + self.to_update_keys + self.to_delete_keys if len(all_change_keys) == 0: return True else: return False def _get_binding_key_tuple(self, binding_dict): log('ModuleExecutor._get_binding_key_tuple()') ret_val = [] # Order of attribute values is determined by ordering of self.key_attributes_present for attribute in self.key_attributes_present: if attribute in binding_dict: attribute_value = binding_dict[attribute] ret_val.append(attribute_value) return tuple(ret_val) def _binding_list_item_identical_to_configured(self, configured_dict, retrieved_dict): log('ModuleExecutor._binding_list_item_identical_to_configured()') ret_val = True for attribute in configured_dict.keys(): configured_value = configured_dict[attribute] retrieved_value = retrieved_dict.get(attribute) if configured_value != retrieved_value: ret_val = False str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) self.differing_attributes.append(attribute) log('Attribute "%s" differs. Configured parameter: (%s) %s. Retrieved NITRO parameter: (%s) %s' % str_tuple) return ret_val def _binding_list_item_delete(self, binding): log('ModuleExecutor._binding_list_item_delete()') log('Deleting binding %s' % binding) # First attribute is the primary id attribute id_key = self.key_attributes_present[0] id = binding[id_key] args = {} for key in self.key_attributes_present[1:]: if key in binding: # Bool args values need to be lower case otherwise NITRO errors if isinstance(binding[key], bool): if binding[key]: args_value = 'true' else: args_value = 'false' # Default is to pass the value unmodified else: args_value = binding[key] args[key] = args_value result = self.fetcher.delete(resource=self.endpoint, id=id, args=args) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def _binding_list_item_create(self, binding): log('ModuleExecutor._binding_list_item_create()') put_data = {self.endpoint: binding} log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource=self.endpoint) log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def bindings_list_create(self): log('ModuleExecutor.bindings_list_create()') self.bindings_list_update() def bindings_list_update(self): log('ModuleExecutor.bindings_list_update()') for key in self.to_delete_keys: self._binding_list_item_delete(self.existing_bindings_dict[key]) for key in self.to_update_keys: self._binding_list_item_delete(self.existing_bindings_dict[key]) for key in self.to_update_keys: self._binding_list_item_create(self.configured_bindings_dict[key]) for key in self.to_create_keys: self._binding_list_item_create(self.configured_bindings_dict[key]) def bindings_list_delete(self): log('ModuleExecutor.bindings_list_delete()') for key in self.configured_bindings_dict: binding = self.configured_bindings_dict[key] self._binding_list_item_delete(binding) def object_exists(self): log('ModuleExecutor.object_exists()') resource_missing_errorcode = self.module.params['workflow'].get( 'resource_missing_errorcode') log('resource missing errorcode %s' % resource_missing_errorcode) if resource_missing_errorcode is None: msg = 'object lifecycle requires resource_missing_errorcode workflow parameter' self.module.fail_json(msg=msg, **self.module_result) result = self.fetcher.get(self.endpoint, self.id) log('get result %s' % result) if result['nitro_errorcode'] == 0: if self.endpoint not in result['data']: return False else: self.retrieved_object = result['data'][self.endpoint][0] return True elif result['nitro_errorcode'] == resource_missing_errorcode: return False else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def object_identical(self): log('ModuleExecutor.object_identical()') ret_val = True if self.retrieved_object is None: raise Exception('Should have a retrieved object by now.') skip_attributes = self.module.params['workflow'].get( 'skip_attributes', []) for attribute in self.module.params['resource'].keys(): # Skip attributes if attribute in skip_attributes: continue configured_value = self.module.params['resource'][attribute] retrieved_value = self.retrieved_object.get(attribute) if configured_value != retrieved_value: ret_val = False str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) self.differing_attributes.append(attribute) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) entry = 'Attribute "%s" differs. Playbook parameter: "%s". Retrieved NITRO object: "%s"' % ( attribute, configured_value, retrieved_value) self.prepared_list.append(entry) return ret_val def object_create(self): log('ModuleExecutor.object_create()') attributes = self.module.params['resource'] post_data = {self.endpoint: attributes} log('post data %s' % post_data) result = self.fetcher.post(post_data=post_data, resource=self.endpoint) log('post result %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result[ 'http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def object_update(self): log('ModuleExecutor.object_update()') non_updateables_changed = list( frozenset(self.non_updateable_attributes) & frozenset(self.differing_attributes)) if len(non_updateables_changed) > 0: log('Non updateables changed %s' % non_updateables_changed) if self.module.params['workflow']['allow_recreate']: self.object_delete() self.object_create() else: msg = ( 'Not allowed to recreate object. Non updateable attributes changed %s' % non_updateables_changed) self.module.fail_json(msg=msg, **self.module_result) else: attributes = self.module.params['resource'] for attribute in self.non_updateable_attributes: if attribute in attributes: del attributes[attribute] put_data = {self.endpoint: attributes} log('request put data: %s' % put_data) result = self.fetcher.put(put_data=put_data, resource=self.endpoint) log('result of put: %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def object_delete(self): log('ModuleExecutor.object_delete()') args = {} for key in self.module.params['workflow'].get('delete_id_attributes', []): if key in self.configured_object: args[key] = self.configured_object[key] result = self.fetcher.delete(resource=self.endpoint, id=self.id, args=args) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def object_by_args_exists(self): log('ModuleExecutor.object_exists()') resource_missing_errorcode = self.module.params['workflow'].get( 'resource_missing_errorcode') log('resource missing errorcode %s' % resource_missing_errorcode) if resource_missing_errorcode is None: msg = 'object_by_args lifecycle requires resource_missing_errorcode workflow parameter' self.module.fail_json(msg=msg, **self.module_result) # We need to id the object through args # We use the delete ids for get as well args = {} for key in self.module.params['workflow'].get('delete_id_attributes', []): if key in self.module.params['resource']: args[key] = self.module.params['resource'][key] result = self.fetcher.get(self.endpoint, args=args) log('get result %s' % result) if result['nitro_errorcode'] == 0: if self.endpoint not in result['data']: return False elif len(result['data'][self.endpoint]) > 1: raise Exception( "Multiple objects retrieved. Should only be one.") else: self.retrieved_object = result['data'][self.endpoint][0] return True elif result['nitro_errorcode'] == resource_missing_errorcode: return False else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def object_by_args_identical(self): log('ModuleExecutor.object_by_args_identical()') return self.object_identical() def object_by_args_create(self): log('ModuleExecutor.object_by_args_create()') self.object_create() def object_by_args_update(self): log('ModuleExecutor.object_by_args_update()') self.object_update() def object_by_args_delete(self): log('ModuleExecutor.object_by_args_delete()') self.object_delete() def non_updateable_object_exists(self): log('ModuleExecutor.non_updateable_object_exists()') resource_missing_errorcode = self.module.params['workflow'].get( 'resource_missing_errorcode') log('resource missing errorcode %s' % resource_missing_errorcode) if resource_missing_errorcode is None: msg = 'object lifecycle requires resource_missing_errorcode workflow parameter' self.module.fail_json(msg=msg, **self.module_result) args = {} for key in self.module.params['workflow'].get('delete_id_attributes', []): if key in self.configured_object: args[key] = self.configured_object[key] log('self.id %s' % self.id) result = self.fetcher.get(self.endpoint, id=self.id, args=args) log('get result %s' % result) if result['nitro_errorcode'] == 0: returned_list = result['data'][self.endpoint] if len(returned_list) > 1: msg = 'Found more than one existing objects' self.module.fail_json(msg=msg, **self.module_result) # Fallthrough self.retrieved_object = result['data'][self.endpoint][0] return True elif result['nitro_errorcode'] == resource_missing_errorcode: return False else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def non_updateable_object_identical(self): log('ModuleExecutor.non_updateable_object_identical()') ret_val = True if self.retrieved_object is None: raise Exception('Should have a retrieved object by now.') for attribute in self.module.params['resource'].keys(): configured_value = self.module.params['resource'][attribute] retrieved_value = self.retrieved_object.get(attribute) if configured_value != retrieved_value: ret_val = False str_tuple = ( attribute, type(configured_value), configured_value, type(retrieved_value), retrieved_value, ) self.differing_attributes.append(attribute) log('Attribute "%s" differs. Playbook parameter: (%s) %s. Retrieved NITRO object: (%s) %s' % str_tuple) entry = 'Attribute "%s" differs. Playbook parameter: "%s". Retrieved NITRO object: "%s"' % ( attribute, configured_value, retrieved_value) self.prepared_list.append(entry) return ret_val def non_updateable_object_create(self): log('ModuleExecutor.non_updateable_object_create()') attributes = self.module.params['resource'] post_data = {self.endpoint: attributes} log('post data %s' % post_data) result = self.fetcher.post(post_data=post_data, resource=self.endpoint) log('post result %s' % result) if result['http_response_data']['status'] == 201: if result.get('nitro_errorcode') is not None: if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) elif 400 <= result['http_response_data']['status'] <= 599: raise NitroException( errorcode=result.get('nitro_errorcode'), message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) else: msg = 'Did not get nitro errorcode and http status was not 201 or 4xx (%s)' % result[ 'http_response_data']['status'] self.module.fail_json(msg=msg, **self.module_result) def non_updateable_object_update(self): log('ModuleExecutor.non_updateable_object_update()') self.non_updateable_object_delete() self.non_updateable_object_create() def non_updateable_object_delete(self): log('ModuleExecutor.non_updateable_object_delete()') args = {} for key in self.module.params['workflow']['delete_id_attributes']: if key in self.configured_object: args[key] = self.configured_object[key] result = self.fetcher.delete(resource=self.endpoint, id=self.id, args=args) log('delete result %s' % result) if result['nitro_errorcode'] != 0: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) def parameter_object_exists(self): log('ModuleExecutor.parameter_object_exists()') result = self.fetcher.get(self.endpoint) log('get result %s' % result) if result['nitro_errorcode'] == 0: if self.endpoint not in result['data']: msg = 'Parameter object does not exist' self.fail_json(msg=msg, **self.module_result) else: self.retrieved_object = result['data'][self.endpoint] if not isinstance(self.retrieved_object, dict): msg = 'Expected dict. Got instead %s' % type( self.retrieved_object) self.fail_json(msg=msg, **self.module_result) # Fallthrough return True else: raise NitroException( errorcode=result['nitro_errorcode'], message=result.get('nitro_message'), severity=result.get('nitro_severity'), ) # Parameter object always exists # We just adjust some values through the update method return True def parameter_object_identical(self): log('ModuleExecutor.parameter_object_identical()') return self.object_identical() def parameter_object_create(self): log('ModuleExecutor.parameter_object_create()') # Since parameter_object_exists always returns true # or errors out this code path should not be reachable raise Exception( 'Create method should not be reachable for parameter_object') def parameter_object_update(self): log('ModuleExecutor.parameter_object_update()') self.object_update() def parameter_object_delete(self): log('ModuleExecutor.parameter_object_delete()') # This is noop for this kind of object # You cannot delete the parameter configuration def update_or_create_resource(self): log('ModuleExecutor.update_or_create_resource()') # Create or update main object if not self.resource_exists(): self.module_result['changed'] = True self.prepared_list.append('Create resource') if not self.module.check_mode: self.resource_create() else: if not self.resource_identical(): self.module_result['changed'] = True if not self.module.check_mode: self.resource_update() else: log('Existing resource has identical values to configured.') def delete_resource(self): log('ModuleExecutor.delete_resource()') if self.resource_exists(): self.module_result['changed'] = True self.prepared_list.append('Delete resource') if not self.module.check_mode: self.resource_delete() def main(self): try: if self.module.params['state'] == 'present': self.update_or_create_resource() elif self.module.params['state'] == 'absent': self.delete_resource() if self.module._diff: self.module_result['diff'] = { 'prepared': '\n'.join(self.prepared_list) } self.module.exit_json(**self.module_result) except NitroException as e: msg = "nitro exception errorcode=%s, message=%s, severity=%s" % ( str(e.errorcode), e.message, e.severity) self.module.fail_json(msg=msg, **self.module_result) except Exception as e: msg = 'Exception %s: %s' % (type(e), str(e)) self.module.fail_json(msg=msg, **self.module_result)