Esempio n. 1
0
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)
Esempio n. 3
0
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)
Esempio n. 5
0
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)