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 = {
            'appfwsignatures': {
                'attributes_list': [
                    'name',
                    'src',
                    'xslt',
                    'xslt_builtin',
                ],
                'transforms': {},
                'get_id_attributes': [
                    'name',
                ],
                'delete_id_attributes': [
                    'name',
                ],
                'non_updateable_attributes': [],
            },
        }

        self.module_result = dict(
            changed=False,
            failed=False,
            loglines=loglines,
        )

        self.init_tmp_dir()
        self.calculate_configured_signatures()

        self.local_native_signatures = os.path.join(
            self.tmp_dir, self.configured_signatures['name'])

    def calculate_configured_signatures(self):
        log('ModuleExecutor.calculate_configured_signatures()')
        self.configured_signatures = {}
        for attribute in self.attribute_config['appfwsignatures'][
                'attributes_list']:
            value = self.module.params.get(attribute)
            # Skip null values
            if value is None:
                continue
            transform = self.attribute_config['appfwsignatures'][
                'transforms'].get(attribute)
            if transform is not None:
                value = transform(value)
            self.configured_signatures[attribute] = value

        self.use_builtin_xslt = self.configured_signatures.get(
            'xslt_builtin') is not None

        log('calculated configured appfwsignatures %s' %
            self.configured_signatures)

    def init_tmp_dir(self):
        log('ModuleExecutor.init_tmp_dir()')
        self.tmp_dir = tempfile.mkdtemp(
            prefix='appfw_signatures_custom_import_')
        log('tmp dir is %s' % self.tmp_dir)

    def cleanup_tmp_dir(self):
        log('ModuleExecutor.cleanup_tmp_dir()')

        shutil.rmtree(self.tmp_dir)

    def signatures_exists(self):
        log('ModuleExecutor.signatures_exists()')
        result = self.fetcher.get('appfwsignatures',
                                  self.module.params['name'])

        log('get result %s' % result)
        if result['nitro_errorcode'] == 0:
            return True
        elif result['nitro_errorcode'] == 3380:
            return False
        else:
            raise NitroException(
                errorcode=result['nitro_errorcode'],
                message=result.get('nitro_message'),
                severity=result.get('nitro_severity'),
            )

    def _download_file(self, remote_path, local_path):
        log('ModuleExecutor._download_file()')
        log('remote_path %s' % remote_path)
        log('local_path %s' % local_path)
        args = {}
        args['filename'] = os.path.basename(remote_path)
        args['filelocation'] = os.path.dirname(remote_path)
        result = self.fetcher.get('systemfile', args=args)

        log('get result %s' % result)

        if result['nitro_errorcode'] != 0:
            raise NitroException(
                errorcode=result['nitro_errorcode'],
                message=result.get('nitro_message'),
                severity=result.get('nitro_severity'),
            )

        bytes_received = codecs.encode(
            result['data']['systemfile'][0]['filecontent'])
        retrieved_filecontent = codecs.decode(base64.b64decode(bytes_received))
        with open(local_path, 'w') as fh:
            fh.write(retrieved_filecontent)

    def _ensure_remote_file_delete(self, remote_path):
        log('ModuleExecutor._ensure_remote_file_delete()')

        args = {}
        args['filename'] = os.path.basename(remote_path)
        args['filelocation'] = os.path.dirname(remote_path)
        result = self.fetcher.get('systemfile', args=args)

        log('get result %s' % result)

        # File does not exist
        if result['nitro_errorcode'] == 3441:
            return
        elif result['nitro_errorcode'] != 0:
            raise NitroException(
                errorcode=result['nitro_errorcode'],
                message=result.get('nitro_message'),
                severity=result.get('nitro_severity'),
            )
        # Fallthrough

        result = self.fetcher.delete(
            resource='systemfile',
            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 _upload_file(self, local_path, remote_path):
        log('ModuleExecutor._upload_file()')

        self._ensure_remote_file_delete(remote_path)

        with open(local_path, 'r') as fh:
            file_data = fh.read()

        post_data = {
            'systemfile': {
                'filelocation':
                os.path.dirname(remote_path),
                'filename':
                os.path.basename(remote_path),
                'filecontent':
                codecs.decode(base64.b64encode(codecs.encode(file_data))),
            }
        }

        result = self.fetcher.post(post_data=post_data, resource='systemfile')
        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 copy_builtin_xslt_to_vartmp(self):
        log('ModuleExecutor.copy_builtin_xslt_to_vartmp()')

        remote_path = os.path.join(
            '/',
            'netscaler',
            'scan_%s.xsl' % self.configured_signatures['xslt_builtin'],
        )

        local_path = os.path.join(
            self.tmp_dir,
            'scan_%s.xsl' % self.configured_signatures['xslt_builtin'],
        )

        self._download_file(remote_path, local_path)
        vartmp_remote_path = os.path.join(
            '/var/tmp',
            'scan_%s.xsl' % self.configured_signatures['xslt_builtin'],
        )
        self._upload_file(local_path, vartmp_remote_path)

    def download_native_singatures_file(self):
        log('ModuleExecutor.download_native_singatures_file()')
        remote_path = os.path.join('/var/download/custom',
                                   self.configured_signatures['name'])
        local_path = self.local_native_signatures
        if os.path.exists(local_path):
            return

        # Fallthrough to download

        self._download_file(remote_path=remote_path, local_path=local_path)

    def process_native_signatures_file(self):
        log('ModuleExecutor.process_native_signatures_file()')

        tree = ET.parse(self.local_native_signatures)
        root = tree.getroot()

        for rule in root.findall('./Signatures/SignatureRule'):
            self.process_enabled(rule)
            self.process_actions(rule)

        tree.write(self.local_native_signatures,
                   encoding='UTF-8',
                   xml_declaration=True)

    def process_enabled(self, rule):
        log('ModuleExecutor.process_enabled()')
        want_enabled = self._rule_want_enabled(rule)
        if want_enabled:
            rule.attrib['enabled'] = 'ON'
        else:
            if 'enabled' in rule.attrib:
                del rule.attrib['enabled']

    def process_actions(self, rule):
        log('ModuleExecutor.process_actions()')
        desired_actions = self._desired_actions_string(rule)
        if desired_actions != rule.attrib['actions']:
            rule.attrib['actions'] = desired_actions

    def initial_create_signatures(self):
        log('ModuleExecutor.initial_create_signatures()')

        post_data = {
            'name': self.configured_signatures['name'],
            'src': self.configured_signatures['src'],
            'preservedefactions': False,
        }

        if self.use_builtin_xslt:
            post_data[
                'xslt'] = 'local:scan_%s.xsl' % self.configured_signatures[
                    'xslt_builtin']
        else:
            post_data['xslt'] = self.configured_signatures['xslt']

        log('post_data: %s' % post_data)
        self._do_import_action(post_data)

    def _do_import_action(self, post_data):
        log('ModuleExecutor._do_import_action()')

        post_data = {'appfwsignatures': post_data}

        result = self.fetcher.post(
            resource='appfwsignatures?action=Import',
            post_data=post_data,
            #action='Import',
        )
        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:
            raise Exception(
                'Did not get nitro errorcode and http status was not 200 or 4xx (%s)'
                % result['http_response_data']['status'])

    def _do_update_action(self, post_data):
        log('ModuleExecutor._do_update_action()')

        post_data = {'appfwsignatures': post_data}

        result = self.fetcher.post(
            resource='appfwsignatures',
            post_data=post_data,
            action='update',
        )
        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:
            raise Exception(
                'Did not get nitro errorcode and http status was not 200 or 4xx (%s)'
                % result['http_response_data']['status'])

    def create_signatures(self):
        log('ModuleExecutor.create_signatures()')

        if self.use_builtin_xslt:
            self.copy_builtin_xslt_to_vartmp()

        self.initial_create_signatures()

        self.update_signatures()

    def update_signatures(self):
        log('ModuleExecutor.update_signatures()')

        self.download_native_singatures_file()

        self.process_native_signatures_file()

        self.upload_and_update_native_signatures_file()

    def upload_and_update_native_signatures_file(self):
        log('ModuleExecutor.upload_and_update_native_signatures_file()')

        local_path = self.local_native_signatures

        native_signatures_filename = os.path.basename(
            self.local_native_signatures)
        remote_path = os.path.join('/var/tmp', native_signatures_filename)

        self._upload_file(local_path, remote_path)

        post_data = {
            'name': self.configured_signatures['name'],
            'src': 'local:%s' % native_signatures_filename,
            'overwrite': 'true',
        }

        self._do_import_action(post_data)

        post_data = {
            'name': self.configured_signatures['name'],
        }

        self._do_update_action(post_data)

    def signatures_identical(self):
        log('ModuleExecutor.signatures_identical()')

        self.download_native_singatures_file()

        tree = ET.parse(self.local_native_signatures)
        root = tree.getroot()

        for rule in root.findall('./Signatures/SignatureRule'):
            is_enabled = self._rule_is_enabled(rule)
            if is_enabled and not self._rule_want_enabled(rule):
                return False
            if not is_enabled and self._rule_want_enabled(rule):
                return False
            desired_actions = self._desired_actions_string(rule)
            if desired_actions != rule.attrib['actions']:
                return False

        # Fallthrough

        return True

    def _rule_is_enabled(self, rule):
        if 'enabled' not in rule.attrib:
            return False
        if rule.attrib['enabled'] != 'ON':
            return False

        # Fallthrough

        return True

    def _rule_want_enabled(self, rule):
        enable_all = self.module.params.get('enable_all')
        if enable_all is not None and enable_all:
            return True

        return False

    def _desired_actions_string(self, rule):
        actions = []
        block_all = self.module.params.get('block_all')
        if block_all is not None and block_all:
            actions.append('block')

        log_all = self.module.params.get('log_all')
        if log_all is not None and log_all:
            actions.append('log')

        stats_all = self.module.params.get('stats_all')
        if stats_all is not None and stats_all:
            actions.append('stats')

        return ','.join(actions)

    def update_or_create(self):
        log('ModuleExecutor.update_or_create()')

        # Create or update main object
        if not self.signatures_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                log('Signatures do not exist. Will create.')
                self.create_signatures()
        else:
            if not self.signatures_identical():
                log('Existing signatures do not have identical values to configured. Will update.'
                    )
                self.module_result['changed'] = True
                if not self.module.check_mode:
                    self.update_signatures()
            else:
                log('Existing signatures have identical values to configured.')

    def delete_signatures(self):
        log('ModuleExecutor.delete_signatures()')

        result = self.fetcher.delete(resource='appfwsignatures',
                                     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.signatures_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                self.delete_signatures()

    def main(self):
        try:

            if self.module.params['state'] == 'present':
                self.update_or_create()
            elif self.module.params['state'] == 'absent':
                self.delete()

            self.cleanup_tmp_dir()
            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)
Пример #2
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)
def main():

    argument_spec = dict()

    module_specific_arguments = dict(
        username=dict(
            type='str',
            required=True,
        ),
        password=dict(
            type='str',
            required=True,
            no_log=True,
        ),
        new_password=dict(
            type='str',
            required=True,
            no_log=True,
        ),
        nsip=dict(
            type='str',
            required=True,
        ),
        nitro_protocol=dict(
            required=True,
            type='str',
            choices=['https', 'http'],
        ),
        validate_certs=dict(required=True, type='bool'),
    )

    argument_spec.update(module_specific_arguments)

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=False,
    )
    post_data = {
        'login': {
            'username': module.params['username'],
            'password': module.params['password'],
            'new_password': module.params['new_password'],
        }
    }

    fetcher = NitroAPIFetcher(module)
    result = fetcher.post(post_data=post_data, resource='login')

    module_result = dict(
        changed=False,
        failed=False,
        loglines=loglines,
    )

    log('{0}'.format(result))
    if result['nitro_errorcode'] == 0:
        module_result['changed'] = True
        module.exit_json(**module_result)
    else:
        msg = 'Non zero nitro errorcode {0}'.format(result['nitro_errorcode'])
        module.fail_json(msg=msg, **module_result)
Пример #4
0
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 = {
            'dnsnsrec': {
                'attributes_list': [
                    'domain',
                    'nameserver',
                    'ttl',
                ],
                'transforms': {},
                'get_id_attributes': [
                    'domain',
                ],
                'delete_id_attributes': [
                    'domain',
                    'nameserver',
                    'ecssubnet',
                ],
                '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_dnsnsrec()

    def calculate_configured_dnsnsrec(self):
        log('ModuleExecutor.calculate_configured_dnsnsrec()')
        self.configured_dnsnsrec = {}
        for attribute in self.attribute_config['dnsnsrec']['attributes_list']:
            value = self.module.params.get(attribute)
            # Skip null values
            if value is None:
                continue
            transform = self.attribute_config['dnsnsrec']['transforms'].get(
                attribute)
            if transform is not None:
                value = transform(value)
            self.configured_dnsnsrec[attribute] = value

        log('calculated configured dnsnsrec %s' % self.configured_dnsnsrec)

    def dnsnsrec_exists(self):
        log('ModuleExecutor.dnsnsrec_exists()')
        result = self.fetcher.get('dnsnsrec')

        log('get result %s' % result)
        if result['nitro_errorcode'] != 0:
            raise NitroException(
                errorcode=result['nitro_errorcode'],
                message=result.get('nitro_message'),
                severity=result.get('nitro_severity'),
            )
        # Sort though the bound ciphers for cipheraliasname match
        for dnsnsrec in result['data'].get('dnsnsrec', []):
            match = all(
                (dnsnsrec['domain'] == self.configured_dnsnsrec['domain'],
                 dnsnsrec['nameserver'] ==
                 self.configured_dnsnsrec['nameserver']))
            if match:
                return True

        # Fallthrough
        return False

    def create_dnsnsrec(self):
        log('ModuleExecutor.create_dnsnsrec()')

        post_data = {'dnsnsrec': self.configured_dnsnsrec}

        result = self.fetcher.post(post_data=post_data, resource='dnsnsrec')
        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_dnsnsrec(self):
        log('ModuleExecutor.update_dnsnsrec()')

        self.delete_dnsnsrec()
        self.create_dnsnsrec()

    def dnsnsrec_identical(self):
        log('ModuleExecutor.dnsnsrec_identical()')
        result = self.fetcher.get('dnsnsrec')
        retrieved_dnsnsrecs = result['data'].get('dnsnsrec', [])

        if result['nitro_errorcode'] != 0:
            raise NitroException(
                errorcode=result['nitro_errorcode'],
                message=result.get('nitro_message'),
                severity=result.get('nitro_severity'),
            )

        diff_list = []
        # Iterate over keys that already exist in the playbook
        for retrieved_record in retrieved_dnsnsrecs:

            # Skip irrelevant ciphers
            match = all((retrieved_record['domain'] ==
                         self.configured_dnsnsrec['domain'],
                         retrieved_record['nameserver'] ==
                         self.configured_dnsnsrec['nameserver']))
            if not match:
                continue

            for attribute in self.configured_dnsnsrec.keys():
                retrieved_value = retrieved_record.get(attribute)
                configured_value = self.configured_dnsnsrec.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)
            self.module_result['diff_list'] = diff_list

        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.dnsnsrec_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                log('dnsnsrec does not exist. Will create.')
                self.create_dnsnsrec()
        else:
            if not self.dnsnsrec_identical():
                log('Existing dnsnsrec does not have identical values to configured. Will update.'
                    )
                self.module_result['changed'] = True
                if not self.module.check_mode:
                    self.update_dnsnsrec()
            else:
                log('Existing dnsnsrec has identical values to configured.')

    def delete_dnsnsrec(self):
        log('ModuleExecutor.delete_dnsnsrec()')

        args = {'nameserver': self.configured_dnsnsrec.get('nameserver')}
        result = self.fetcher.delete(resource='dnsnsrec',
                                     id=self.module.params['domain'],
                                     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.dnsnsrec_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                self.delete_dnsnsrec()

    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 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)
Пример #5
0
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 = {
            'systemfile': {
                'attributes_list': [
                    'filename',
                    'filecontent',
                    'filelocation',
                    'fileencoding',
                ],
                'transforms': {
                },
                'get_id_attributes': [
                ],
                'delete_id_attributes': [
                    'filename',
                    'filelocation',
                ],
                '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_systemfile()

    def calculate_configured_systemfile(self):
        log('ModuleExecutor.calculate_configured_systemfile()')
        self.configured_systemfile = {}
        for attribute in self.attribute_config['systemfile']['attributes_list']:
            value = self.module.params.get(attribute)
            # Skip null values
            if value is None:
                continue
            transform = self.attribute_config['systemfile']['transforms'].get(attribute)
            if transform is not None:
                value = transform(value)
            self.configured_systemfile[attribute] = value

        log('calculated configured systemfile %s' % self.configured_systemfile)

    def systemfile_exists(self):
        log('ModuleExecutor.systemfile_exists()')
        args = {}
        args['filename'] = self.module.params['filename']
        args['filelocation'] = self.module.params['filelocation']
        result = self.fetcher.get('systemfile', args=args)

        log('get result %s' % result)

        # File does not exist
        if result['nitro_errorcode'] == 3441:
            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 systemfile_identical()
        self.retrieved_systemfile = result['data']['systemfile'][0]

        return True

    def create_systemfile(self):
        log('ModuleExecutor.create_systemfile()')

        post_data = copy.deepcopy(self.configured_systemfile)
        post_data['filecontent'] = codecs.decode(base64.b64encode(codecs.encode(post_data['filecontent'])))
        post_data = {
            'systemfile': post_data,
        }

        result = self.fetcher.post(post_data=post_data, resource='systemfile')
        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_systemfile(self):
        log('ModuleExecutor.update_systemfile()')

        self.delete_systemfile()
        self.create_systemfile()

    def systemfile_identical(self):
        log('ModuleExecutor.systemfile_identical()')

        diff_list = []

        # Only the filecontents is considered for equality
        # systemfile_exists has already tested filelocation and filename for equality to be true
        bytes_received = codecs.encode(self.retrieved_systemfile['filecontent'])
        retrieved_filecontent = codecs.decode(base64.b64decode(bytes_received))
        configured_filecontent = self.configured_systemfile['filecontent']

        if retrieved_filecontent != configured_filecontent:
            str_tuple = (
                'filecontent',
                type(configured_filecontent),
                configured_filecontent,
                type(retrieved_filecontent),
                retrieved_filecontent,
            )
            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 update_or_create(self):
        log('ModuleExecutor.update_or_create()')

        # Create or update main object
        if not self.systemfile_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                log('systemfile does not exist. Will create.')
                self.create_systemfile()
        else:
            if not self.systemfile_identical():
                log('Existing systemfile does not have identical values to configured. Will update.')
                self.module_result['changed'] = True
                if not self.module.check_mode:
                    self.update_systemfile()
            else:
                log('Existing systemfile has identical values to configured.')

    def delete_systemfile(self):
        log('ModuleExecutor.delete_systemfile()')

        args = {
            'filename': self.configured_systemfile['filename'],
            'filelocation': self.configured_systemfile['filelocation'],
        }
        result = self.fetcher.delete(
            resource='systemfile',
            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.systemfile_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                self.delete_systemfile()

    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 = 'servicegroup'

        # Dictionary containing attribute information
        # for each NITRO object utilized by this module
        self.attribute_config = {
            'sslcipher': {
                'attributes_list': [
                    'ciphergroupname',
                ],
                'transforms': {},
                'get_id_attributes': [
                    'ciphergroupname',
                ],
                'delete_id_attributes': [
                    'ciphergroupname',
                    'ciphername',
                ],
                '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_cipher()

    def calculate_configured_cipher(self):
        log('ModuleExecutor.calculate_configured_cipher()')
        self.configured_cipher = {}
        for attribute in self.attribute_config['sslcipher']['attributes_list']:
            value = self.module.params.get(attribute)
            # Skip null values
            if value is None:
                continue
            transform = self.attribute_config['sslcipher']['transforms'].get(
                attribute)
            if transform is not None:
                value = transform(value)
            self.configured_cipher[attribute] = value

        log('calculated configured sslcipher %s' % self.configured_cipher)

    def cipher_exists(self):
        log('ModuleExecutor.cipher_exists()')
        result = self.fetcher.get('sslcipher',
                                  self.module.params['ciphergroupname'])

        log('get result %s' % result)
        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
        return True

    def create_cipher(self):
        log('ModuleExecutor.create_cipher()')

        post_data = {'sslcipher': self.configured_cipher}

        result = self.fetcher.post(post_data=post_data, resource='sslcipher')
        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_or_create(self):
        log('ModuleExecutor.update_or_create()')

        # sslcipher only valid attribute is its ciphergroupname
        # It either exists or not. ugdate is not sensible
        if not self.cipher_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                log('cipher does not exist. Will create.')
                self.create_cipher()
        else:
            self.module_result['changed'] = False

    def delete_cipher(self):
        log('ModuleExecutor.delete_cipher()')

        result = self.fetcher.delete(
            resource='sslcipher',
            id=self.module.params['ciphergroupname'],
        )
        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.cipher_exists():
            self.module_result['changed'] = True
            if not self.module.check_mode:
                self.delete_cipher()

    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 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 = {
            '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)
Пример #9
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)
Пример #10
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)