class ManageIQTagAssignment(object):
    """ ManageIQ object to execute tag assignments in manageiq

    url            - manageiq environment url
    user           - the username in manageiq
    password       - the user password in manageiq
    miq_verify_ssl - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """

    manageiq_entities = {
        'provider': 'providers',
        'host': 'hosts',
        'vm': 'vms',
        'category': 'categories',
        'cluster': 'clusters',
        'data store': 'data_stores',
        'group': 'groups',
        'resource pool': 'resource_pools',
        'service': 'services',
        'service template': 'service_templates',
        'template': 'templates',
        'tenant': 'tenants',
        'user': '******',
        'blueprint': 'blueprints'
    }
    actions = {'present': 'assign', 'absent': 'unassign'}

    def __init__(self, module, url, user, password, miq_verify_ssl,
                 ca_bundle_path):
        self.module = module
        self.api_url = url + '/api'
        self.user = user
        self.password = password
        self.client = MiqApi(self.api_url, (self.user, self.password),
                             verify_ssl=miq_verify_ssl,
                             ca_bundle_path=ca_bundle_path)
        self.changed = False

    def find_entity_by_name(self, entity_type, entity_name):
        """ Searches the entity name in ManageIQ.

        Returns:
            the entity id if it exists in manageiq, None otherwise.
        """
        entities_list = getattr(self.client.collections, entity_type)
        return next((e.id for e in entities_list if e.name == entity_name),
                    None)

    def query_resource_tags(self, resource_type, resource_id):
        """ Returns a set of the full tag names assigned to the resource
        """
        try:
            url = '{api_url}/{resource_type}/{resource_id}/tags?expand=resources'.format(
                api_url=self.api_url,
                resource_type=resource_type,
                resource_id=resource_id)
            response = self.client.get(url)
        except Exception as e:
            self.module.fail_json(
                msg="Failed to query {resource_type} tags: {error}".format(
                    resource_type=resource_type, error=e))
        tags = response.get('resources', [])
        tags_set = set([tag['name'] for tag in tags])
        return tags_set

    def execute_action(self, resource_type, resource_id, tags, action):
        """Executes the action for the resource tag
        """
        url = '{api_url}/{resource_type}/{resource_id}/tags'.format(
            api_url=self.api_url,
            resource_type=resource_type,
            resource_id=resource_id)
        try:
            response = self.client.post(url, action=action, resources=tags)
        except Exception as e:
            self.module.fail_json(msg="Failed to {action} tag: {error}".format(
                action=action, error=e))
        for result in response['results']:
            if result['success']:
                self.changed = True
            else:
                self.module.fail_json(msg="Failed to {action}: {fail_message}".
                                      format(action=action,
                                             entity=entity,
                                             fail_message=result['message']))

    def full_tag_name(self, tag):
        """ Returns the full tag name in manageiq
        """
        full_tag_name = '/managed/{category_name}/{tag_name}'.format(
            category_name=tag['category'], tag_name=tag['name'])
        return full_tag_name

    def assign_or_unassign_tag(self, tags, resource, resource_name, state):
        """ Assign or unassign the tag on a manageiq resource.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        resource_type = self.manageiq_entities[resource]
        resource_id = self.find_entity_by_name(resource_type, resource_name)
        if not resource_id:  # resource doesn't exist
            self.module.fail_json(
                msg=
                "Failed to {action} tag: {resource_name} {resource} does not exist in manageiq"
                .format(action=ManageIQTagAssignment.actions[state],
                        resource_name=resource_name,
                        resource=resource))

        tags_to_execute = []
        assigned_tags = self.query_resource_tags(resource_type, resource_id)
        for tag in tags:
            assigned = self.full_tag_name(tag) in assigned_tags

            if assigned and state == 'absent':
                tags_to_execute.append(tag)
            elif (not assigned) and state == 'present':
                tags_to_execute.append(tag)

        if not tags_to_execute:
            return dict(changed=self.changed,
                        msg="Tags already {action}ed, nothing to do".format(
                            action=ManageIQTagAssignment.actions[state]))
        else:
            self.execute_action(resource_type, resource_id, tags_to_execute,
                                ManageIQTagAssignment.actions[state])
            return dict(changed=self.changed,
                        msg="Successfully {action}ed tags".format(
                            action=ManageIQTagAssignment.actions[state]))
예제 #2
0
class ManageIQProvider(object):
    """ ManageIQ object to execute various operations in manageiq

    url            - manageiq environment url
    user           - the username in manageiq
    password       - the user password in manageiq
    miq_verify_ssl - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """

    OPENSHIFT_DEFAULT_PORT = '8443'

    PROVIDER_TYPES = {
        'openshift-origin':
        'ManageIQ::Providers::Openshift::ContainerManager',
        'openshift-enterprise':
        'ManageIQ::Providers::OpenshiftEnterprise::ContainerManager',
        'amazon':
        'ManageIQ::Providers::Amazon::CloudManager',
        'hawkular-datawarehouse':
        "ManageIQ::Providers::Hawkular::DatawarehouseManager",
    }

    WAIT_TIME = 5
    ITERATIONS = 10

    def __init__(self, module, url, user, password, miq_verify_ssl,
                 ca_bundle_path):
        self.module = module
        self.api_url = url + '/api'
        self.user = user
        self.password = password
        self.client = MiqApi(self.api_url, (self.user, self.password),
                             verify_ssl=miq_verify_ssl,
                             ca_bundle_path=ca_bundle_path)
        self.changed = False
        self.providers_url = self.api_url + '/providers'

    def auths_validation_details(self, provider_id):
        try:
            result = self.client.get(
                '{providers_url}/{id}/?attributes=authentications'.format(
                    providers_url=self.providers_url, id=provider_id))
            auths = result.get('authentications', [])
            return {auth['authtype']: auth for auth in auths}
        except Exception as e:
            self.module.fail_json(
                msg="Failed to get provider data. Error: {!r}".format(e))

    def verify_authenticaion_validation(self, provider_id,
                                        old_validation_details,
                                        authtypes_to_verify):
        """ Verifies that the provider's authentication validation passed.
        provider_id            - the provider's id manageiq
        old_validation_details - a tuple of (last_valid_on, last_invalid_on), representing the last time
                                 that the authentication validation occured (success or failure).
        authtypes_to_verify    - a list of autentication types that require validation

        Returns a (result, details) tuple:
            result: 'Valid' if authentication validation passed for all endpoints, 'Invalid' if failed for any endpoint,
                    'Timed out' if any validation didn't complete in the assigned time
            details: Authentication validation details, 'Validation didn't complete' in case it timed out
        """
        def validated(old, new):
            """ Returns True if the validation timestamp, valid or invalid, is different
            from the old validation timestamp, False otherwise
            """
            return ((old.get('last_valid_on'), old.get('last_invalid_on')) !=
                    (new.get('last_valid_on'), new.get('last_invalid_on')))

        for i in range(ManageIQProvider.ITERATIONS):
            new_validation_details = self.auths_validation_details(provider_id)

            validations_done = True
            all_done_valid = "Valid"  # Out of the (re)validated ones.
            details = {}
            for t in authtypes_to_verify:
                old = old_validation_details.get(t, {})
                new = new_validation_details.get(t, {})
                if not validated(old, new):
                    details[t] = "Validation didn't complete"
                    validations_done = False
                else:
                    details[t] = (new.get('status'), new.get('status_details'))
                    if new.get('status') != 'Valid':
                        all_done_valid = "Invalid"

            if validations_done:
                return all_done_valid, details
            time.sleep(ManageIQProvider.WAIT_TIME)

        return "Timed out", details

    def get_provider_config(self, provider_id):
        """ get the endpoint content of existing provider from manageiq API"""
        try:
            result = self.client.get(
                '{providers_url}/{id}/?attributes=endpoints'.format(
                    providers_url=self.providers_url, id=provider_id))
            return result
        except Exception as e:
            self.module.fail_json(
                msg="Failed to get provider data. Error: {!r}".format(e))

    def required_updates(self, provider_id, endpoints, zone_id,
                         provider_region, existing_config):
        """ Checks whether an update is required for the provider

        Returns:
            Empty Hash (None) - If the hostname, port, zone and region passed equals
                                the provider's current values
            Hash of Changes   - Changes that need to be made if any endpoint, zone
                                or region are different than the current values of the
                                provider. The hash will have three entries:
                                    Updated, Removed, Added
                                that will contain all the changed endpoints
                                and their values.
        """
        def host_port_ssl(endpoint):
            return {
                'hostname': endpoint.get('hostname'),
                'port': endpoint.get('port'),
                'verify_ssl': endpoint.get('verify_ssl'),
                'certificate_authority': endpoint.get('certificate_authority'),
                'security_protocol': endpoint.get('security_protocol')
            }

        desired_by_role = {
            e['endpoint']['role']: host_port_ssl(e['endpoint'])
            for e in endpoints
        }
        existing_by_role = {
            e['role']: host_port_ssl(e)
            for e in existing_config['endpoints']
        }
        existing_provider_region = existing_config.get(
            'provider_region') or None
        if existing_by_role == desired_by_role and existing_config[
                'zone_id'] == zone_id and existing_provider_region == provider_region:
            return {}
        updated = {
            role: {
                k: v
                for k, v in ep.items() if k not in existing_by_role[role]
                or v != existing_by_role[role][k]
            }
            for role, ep in desired_by_role.items()
            if role in existing_by_role and ep != existing_by_role[role]
        }
        added = {
            role: ep
            for role, ep in desired_by_role.items()
            if role not in existing_by_role
        }
        removed = {
            role: ep
            for role, ep in existing_by_role.items()
            if role not in desired_by_role
        }
        if existing_config['zone_id'] != zone_id:
            updated['zone_id'] = zone_id
        if existing_provider_region != provider_region:
            updated['provider_region'] = provider_region
        return {"Updated": updated, "Added": added, "Removed": removed}

    def refresh_provider(self, provider_id):
        """ Performs a refresh of provider's inventory
        """
        try:
            self.client.post('{api_url}/providers/{id}'.format(
                api_url=self.api_url, id=provider_id),
                             action='refresh')
            self.changed = True
        except Exception as e:
            self.module.fail_json(
                msg="Failed to refresh provider. Error: {!r}".format(e))

    def update_provider(self, provider_id, provider_name, endpoints, zone_id,
                        provider_region):
        """ Updates the existing provider with new parameters
        """
        try:
            self.client.post('{api_url}/providers/{id}'.format(
                api_url=self.api_url, id=provider_id),
                             action='edit',
                             zone={'id': zone_id},
                             connection_configurations=endpoints,
                             provider_region=provider_region)
            self.changed = True
        except Exception as e:
            self.module.fail_json(
                msg="Failed to update provider. Error: {!r}".format(e))

    def add_new_provider(self, provider_name, provider_type, endpoints,
                         zone_id, provider_region):
        """ Adds a provider to manageiq

        Returns:
            the added provider id
        """
        try:
            result = self.client.post(
                self.providers_url,
                name=provider_name,
                type=ManageIQProvider.PROVIDER_TYPES[provider_type],
                zone={'id': zone_id},
                connection_configurations=endpoints,
                provider_region=provider_region)
            provider_id = result['results'][0]['id']
            self.changed = True
        except Exception as e:
            self.module.fail_json(
                msg="Failed to add provider. Error: {!r}".format(e))
        return provider_id

    def find_zone_by_name(self, zone_name):
        """ Searches the zone name in manageiq existing zones

        Returns:
            the zone id if it exists in manageiq, None otherwise
        """
        zones = self.client.collections.zones
        return next((z.id for z in zones if z.name == zone_name), None)

    def find_provider_by_name(self, provider_name):
        """ Searches the provider name in manageiq existing providers

        Returns:
            the provider id if it exists in manageiq, None otherwise
        """
        providers = self.client.collections.providers
        return next((p.id for p in providers if p.name == provider_name), None)

    def generate_auth_key_config(self, role, authtype, hostname, port, token,
                                 provider_verify_ssl, provider_ca_path):
        """ Returns an openshift provider endpoint dictionary.
        """
        config = {
            'endpoint': {
                'role': role,
                'hostname': hostname,
                'port': int(port),
                'verify_ssl': provider_verify_ssl
            },
            'authentication': {
                'authtype': authtype,
                'auth_key': token
            }
        }

        if provider_ca_path:
            with open(provider_ca_path, 'r') as provider_ca_file:
                provider_ca_content = provider_ca_file.read()
                config['endpoint'][
                    'certificate_authority'] = provider_ca_content
        else:
            config['endpoint']['certificate_authority'] = None

        # deduce security_protocol from provider_verify_ssl and provider_ca_path
        if provider_verify_ssl:
            if provider_ca_path:
                config['endpoint'][
                    'security_protocol'] = 'ssl-with-validation-custom-ca'
            else:
                config['endpoint']['security_protocol'] = 'ssl-with-validation'
        else:
            config['endpoint']['security_protocol'] = 'ssl-without-validation'

        return config

    def generate_amazon_config(self, role, authtype, userid, password):
        """ Returns an amazon provider endpoint dictionary.
        """
        return {
            'endpoint': {
                'role': role
            },
            'authentication': {
                'authtype': authtype,
                'userid': userid,
                'password': password
            }
        }

    def delete_provider(self, provider_name):
        """ Deletes the provider

        Returns:
            the delete task id if a task was generated, whether or not
            a change took place and a short message describing the operation
            executed.
        """
        provider_id = self.find_provider_by_name(provider_name)
        if provider_id:
            try:
                url = '{providers_url}/{id}'.format(
                    providers_url=self.providers_url, id=provider_id)
                result = self.client.post(url, action='delete')
                if result['success']:
                    self.changed = True
                    return dict(task_id=result['task_id'],
                                changed=self.changed,
                                msg=result['message'])
                else:
                    return dict(
                        task_id=None,
                        changed=self.changed,
                        api_error=result,
                        msg="Failed to delete {provider_name} provider".format(
                            provider_name=provider_name))
            except Exception as e:
                self.module.fail_json(
                    msg=
                    "Failed to delete {provider_name} provider. Error: {error!r}"
                    .format(provider_name=provider_name, error=e))
        else:
            return dict(task_id=None,
                        changed=self.changed,
                        msg="Provider {provider_name} doesn't exist".format(
                            provider_name=provider_name))

    def filter_unsupported_fields_from_config(self, configs,
                                              existing_endpoints, fields):
        """
        Only update fields that already exist in the endpoint with empty values.
        :param configs: New configuration. method mutates this param inplace
        :param existing_endpoints: current provider endpoints
        :param fields: a list of fields that we want to check if already exist in provider, and if not remove empty occurences from endpoints
        """
        for field in fields:
            if not any(field in e for e in existing_endpoints):
                for c in configs:
                    endpoint = c['endpoint']
                    if field in endpoint and endpoint[field] is None:
                        del endpoint[field]

    def add_or_update_provider(self,
                               provider_name,
                               provider_type,
                               endpoints,
                               zone,
                               provider_region,
                               validate_provider_auth=True,
                               initiate_refresh=True):
        """ Adds a provider to manageiq or update its attributes in case
        a provider with the same name already exists

        Returns:
            the added or updated provider id, whether or not a change took
            place and a short message describing the operation executed,
            including the authentication validation status
        """
        zone_id = self.find_zone_by_name(zone or 'default')
        # check if provider with the same name already exists
        provider_id = self.find_provider_by_name(provider_name)
        if provider_id:  # provider exists
            existing_config = self.get_provider_config(provider_id)

            # ManageIQ Euwe / CFME 5.7 API and older versions don't support certificate authority field in endpoint.
            # If it wasn't returned from existing provider configuration this means it is either unsupported or null,
            # in both cases we can remove null/empty certificate_authority from endpoints we want to update.
            self.filter_unsupported_fields_from_config(
                endpoints, existing_config['endpoints'],
                {'certificate_authority'})

            updates = self.required_updates(provider_id, endpoints, zone_id,
                                            provider_region, existing_config)

            if not updates:
                return dict(changed=self.changed,
                            msg="Provider %s already exists" % provider_name)

            old_validation_details = self.auths_validation_details(provider_id)
            operation = "update"
            self.update_provider(provider_id, provider_name, endpoints,
                                 zone_id, provider_region)
            roles_with_changes = set(updates["Added"]) | set(
                updates["Updated"])
        else:  # provider doesn't exists, adding it to manageiq

            # ManageIQ Euwe / CFME 5.7 API and older versions don't support certificate authority field in endpoint.
            # filter empty fields if none on creation - No existing endpoints for new provider
            self.filter_unsupported_fields_from_config(
                endpoints, [{}], {'certificate_authority'})
            updates = None
            old_validation_details = {}
            operation = "addition"
            provider_id = self.add_new_provider(provider_name, provider_type,
                                                endpoints, zone_id,
                                                provider_region)
            roles_with_changes = [e['endpoint']['role'] for e in endpoints]

        if validate_provider_auth:
            authtypes_to_verify = []
            for e in endpoints:
                if e['endpoint']['role'] in roles_with_changes:
                    # todo: Temporary hack. Remove this line when manageiq supports prometheus validation
                    if e['authentication']['authtype'] != 'prometheus':
                        authtypes_to_verify.append(
                            e['authentication']['authtype'])
            result, details = self.verify_authenticaion_validation(
                provider_id, old_validation_details, authtypes_to_verify)
        else:
            result = "Skipped Validation"
            details = result

        if result == "Invalid":
            self.module.fail_json(
                msg=
                "Failed to Validate provider authentication after {operation}. details: {details}"
                .format(operation=operation, details=details))
        elif result == "Valid" or result == "Skipped Validation":
            if initiate_refresh:
                self.refresh_provider(provider_id)
                message = "Successful {operation} of {provider} provider. Authentication: {validation}. Refreshing provider inventory".format(
                    operation=operation,
                    provider=provider_name,
                    validation=details)
            else:
                message = "Successful {operation} of {provider} provider. Authentication: {validation}.".format(
                    operation=operation,
                    provider=provider_name,
                    validation=details)
        elif result == "Timed out":
            message = "Provider {provider} validation after {operation} timed out. Authentication: {validation}".format(
                operation=operation,
                provider=provider_name,
                validation=details)
        return dict(provider_id=provider_id,
                    changed=self.changed,
                    msg=message,
                    updates=updates)
예제 #3
0
class ManageIQUser(object):
    """ ManageIQ object to execute user management operations in manageiq

    url            - manageiq environment url
    user           - the username in manageiq
    password       - the user password in manageiq
    miq_verify_ssl - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """
    def __init__(self, module, url, user, password, miq_verify_ssl,
                 ca_bundle_path):
        self.module = module
        self.api_url = url + '/api'
        self.user = user
        self.password = password
        self.client = MiqApi(self.api_url, (self.user, self.password),
                             verify_ssl=miq_verify_ssl,
                             ca_bundle_path=ca_bundle_path)
        self.changed = False

    def find_group_by_name(self, group_name):
        """ Searches the group name in ManageIQ.

        Returns:
            the group id if it exists in manageiq, None otherwise.
        """
        groups = self.client.collections.groups
        return next(
            (group.id for group in groups if group.description == group_name),
            None)

    def find_user_by_userid(self, userid):
        """ Searches the userid in ManageIQ.

        Returns:
            the user's id if it exists in manageiq, None otherwise.
        """
        users = self.client.collections.users
        return next((user.id for user in users if user.userid == userid), None)

    def delete_user(self, userid):
        """Deletes the user from manageiq.

        Returns:
            a short message describing the operation executed.
        """
        user_id = self.find_user_by_userid(userid)
        if not user_id:  # user doesn't exist
            return dict(changed=self.changed,
                        msg="User {userid} does not exist in manageiq".format(
                            userid=userid))
        try:
            url = '{api_url}/users/{user_id}'.format(api_url=self.api_url,
                                                     user_id=user_id)
            result = self.client.post(url, action='delete')
            self.changed = True
            return dict(changed=self.changed, msg=result['message'])
        except Exception as e:
            self.module.fail_json(
                msg="Failed to delete user {userid}: {error}".format(
                    userid=userid, error=e))

    def user_update_required(self, user_id, userid, username, group_id, email):
        """ Returns true if the username, group id or email passed for the user
            differ from the user's existing ones, False otherwise.
        """
        try:
            url = "{api_url}/users/{user_id}".format(api_url=self.api_url,
                                                     user_id=user_id)
            result = self.client.get(url)
            return result['name'] != username or result[
                'current_group_id'] != group_id or result.get('email') != email
        except Exception as e:
            self.module.fail_json(
                msg="Failed to get user {userid} details. Error: {error}".
                format(userid=userid, error=e))

    def update_user_if_required(self, user_id, userid, username, group_id,
                                password, email):
        """Updates the user in manageiq.

        Returns:
            the created user id, name, created_on timestamp,
            updated_on timestamp, userid and current_group_id
        """
        if not self.user_update_required(user_id, userid, username, group_id,
                                         email):
            return dict(
                changed=self.changed,
                msg="User {userid} already exist, no need for updates".format(
                    userid=userid))
        try:
            url = '{api_url}/users/{user_id}'.format(api_url=self.api_url,
                                                     user_id=user_id)
            resource = {
                'userid': userid,
                'name': username,
                'password': password,
                'group': {
                    'id': group_id
                },
                'email': email
            }
            result = self.client.post(url, action='edit', resource=resource)
            self.changed = True
            return dict(
                changed=self.changed,
                msg="Successfully updated the user {userid}: {user_details}".
                format(userid=userid, user_details=result))
        except Exception as e:
            self.module.fail_json(
                msg="Failed to update user {userid}: {error}".format(
                    userid=userid, error=e))

    def create_user(self, userid, username, group_id, password, email):
        """Creates the user in manageiq.

        Returns:
            the created user id, name, created_on timestamp,
            updated_on timestamp, userid and current_group_id
        """
        try:
            url = '{api_url}/users'.format(api_url=self.api_url)
            resource = {
                'userid': userid,
                'name': username,
                'password': password,
                'group': {
                    'id': group_id
                },
                'email': email
            }
            result = self.client.post(url, action='create', resource=resource)
            self.changed = True
            return dict(
                changed=self.changed,
                msg="Successfully created the user {userid}: {user_details}".
                format(userid=userid, user_details=result['results']))
        except Exception as e:
            self.module.fail_json(
                msg="Failed to create user {userid}: {error}".format(
                    userid=userid, error=e))

    def create_or_update_user(self, userid, username, password, group, email):
        """ Create or update a user in manageiq.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        group_id = self.find_group_by_name(group)
        if not group_id:  # group doesn't exist
            self.module.fail_json(
                msg=
                "Failed to create user {userid}: group {group_name} does not exist in manageiq"
                .format(userid=userid, group_name=group))

        user_id = self.find_user_by_userid(userid)
        if user_id:  # user already exist
            return self.update_user_if_required(user_id, userid, username,
                                                group_id, password, email)
        else:
            return self.create_user(userid, username, group_id, password,
                                    email)
예제 #4
0
class ManageIQCustomAttributes(object):
    """ ManageIQ object to execute custom attibutes related operations
    in manageiq

    url            - manageiq environment url
    user           - the username in manageiq
    password       - the user password in manageiq
    miq_verify_ssl - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """

    supported_entities = {'vm': 'vms', 'provider': 'providers'}

    def __init__(self, module, url, user, password, miq_verify_ssl,
                 ca_bundle_path):
        self.module = module
        self.api_url = url + '/api'
        self.user = user
        self.password = password
        self.client = MiqApi(self.api_url, (self.user, self.password),
                             verify_ssl=miq_verify_ssl,
                             ca_bundle_path=ca_bundle_path)
        self.changed = False

    def find_entity_by_name(self, entity_type, entity_name):
        """ Searches the entity name in ManageIQ.

            Returns:
                the entity id if it exists in manageiq, None otherwise.
        """
        entities_list = getattr(
            self.client.collections,
            ManageIQCustomAttributes.supported_entities[entity_type])
        return next((e.id for e in entities_list if e.name == entity_name),
                    None)

    def get_entity_custom_attributes(self, entity_type, entity_id):
        """ Returns the entity's custom attributes
        """
        try:
            url = '{api_url}/{entity_type}/{id}?expand=custom_attributes'.format(
                api_url=self.api_url,
                entity_type=ManageIQCustomAttributes.
                supported_entities[entity_type],
                id=entity_id)
            result = self.client.get(url)
            return result.get('custom_attributes', [])
        except Exception as e:
            self.module.fail_json(
                msg=
                "Failed to get {entity_type} custom attributes. Error: {error}"
                .format(entity_type=entity_type, error=e))

    def add_custom_attributes(self, entity_type, entity_id, custom_attributes):
        """ Returns the added custom attributes """
        try:
            url = '{api_url}/{entity_type}/{id}/custom_attributes'.format(
                api_url=self.api_url,
                entity_type=ManageIQCustomAttributes.
                supported_entities[entity_type],
                id=entity_id)
            result = self.client.post(url,
                                      action='add',
                                      resources=custom_attributes)
            self.changed = True
            return result['results']
        except Exception as e:
            self.module.fail_json(
                msg="Failed to add the custom attributes. Error: {}".format(e))

    def update_custom_attribute(self, entity_type, entity_id, ca, ca_href):
        """ Returns the updated custom attributes """
        try:
            url = '{api_url}/{entity_type}/{id}/custom_attributes'.format(
                api_url=self.api_url,
                entity_type=ManageIQCustomAttributes.
                supported_entities[entity_type],
                id=entity_id)
            ca_object = {
                'name': ca['name'],
                'href': ca_href,
                'value': ca['value']
            }
            result = self.client.post(url,
                                      action='edit',
                                      resources=[ca_object])
            self.changed = True
            return result['results']
        except Exception as e:
            self.module.fail_json(
                msg=
                "Failed to update the custom attribute {ca_name}. Error: {error}"
                .format(ca_name=ca['name'], error=e))

    @staticmethod
    def compare_custom_attributes(ca1, ca2):
        return (ca1['name'], ca1['section']) == (ca2['name'], ca2['section'])

    def add_or_update_custom_attributes(self, entity_type, entity_name,
                                        custom_attributes):
        """ Adds custom attributes to an entity in manageiq or updates the
        attributes in case already exists

        Returns:
            the added or updated custom attributes, whether or not a change
            took place and a short message describing the operation executed
        """
        added, updated = [], []
        message = ""
        # check if entity with the type and name passed exists in manageiq
        entity_id = self.find_entity_by_name(entity_type, entity_name)
        if not entity_id:  # entity doesn't exist
            self.module.fail_json(
                msg=
                "Failed to set the custom attributes. {entity_type} {entity_name} does not exist"
                .format(entity_type=entity_type, entity_name=entity_name))

        entity_cas = self.get_entity_custom_attributes(entity_type, entity_id)
        for new_ca in custom_attributes:
            existing_ca = next((ca for ca in entity_cas
                                if self.compare_custom_attributes(ca, new_ca)),
                               None)
            if existing_ca:
                if new_ca['value'] != existing_ca['value']:
                    updated.extend(
                        self.update_custom_attribute(entity_type, entity_id,
                                                     new_ca,
                                                     existing_ca['href']))
            else:
                added.extend(
                    self.add_custom_attributes(entity_type, entity_id,
                                               [new_ca]))

        if added or updated:
            message = "Successfully set the custom attributes to {entity_name} {entity_type}"
        else:
            message = "The custom attributes already exist on {entity_name} {entity_type}"

        return dict(changed=self.changed,
                    msg=message.format(entity_name=entity_name,
                                       entity_type=entity_type),
                    updates={
                        "Added": added,
                        "Updated": updated
                    })

    def delete_custom_attribute(self, ca, ca_href, entity_type, entity_id):
        """ Returns the deleted custom attribute
        """
        try:
            url = '{api_url}/{entity_type}/{id}/custom_attributes'.format(
                api_url=self.api_url,
                entity_type=ManageIQCustomAttributes.
                supported_entities[entity_type],
                id=entity_id)
            ca_object = {'name': ca['name'], 'href': ca_href}
            result = self.client.post(url,
                                      action='delete',
                                      resources=[ca_object])
            self.changed = True
            return result['results']
        except Exception as e:
            self.module.fail_json(
                msg="Failed to delete the custom attribute {ca}. Error: {error}"
                .format(ca=ca, error=e))

    def delete_custom_attributes(self, entity_type, entity_name,
                                 custom_attributes):
        """ Deletes the custom attributes from the entity, if exist

        Returns:
            whether or not a change took and a short message including the
            deleted custom attributes
        """
        deleted = []
        entity_id = self.find_entity_by_name(entity_type, entity_name)
        if not entity_id:  # entity doesn't exist
            self.module.fail_json(
                msg=
                "Failed to delete the custom attributes. {entity_type} {entity_name} does not exist"
                .format(entity_type=entity_type, entity_name=entity_name))

        entity_cas = self.get_entity_custom_attributes(entity_type, entity_id)
        for new_ca in custom_attributes:
            ca_href = next((ca['href'] for ca in entity_cas
                            if self.compare_custom_attributes(ca, new_ca)),
                           None)
            if ca_href:
                deleted.extend(
                    self.delete_custom_attribute(new_ca, ca_href, entity_type,
                                                 entity_id))

        return dict(
            msg=
            "Successfully deleted the following custom attributes from {entity_name} {entity_type}: {deleted}"
            .format(entity_name=entity_name,
                    entity_type=entity_type,
                    deleted=deleted),
            changed=self.changed)
}

print("REST API payload = {}".format(payload))

action = client.collections.automation_requests.action
requests = action.execute_action("create", **payload)

# Automation Request API can take multiple requests at once, so the return
# value is an array of requests. Here, we called the API with only one request,
# so we can safely assume that the returned array has only one element.
request = next((item for item in requests if item), None)

# We can get the latest status of the request by GET-ing the URI in request.href.
# Poll status until task.request_status changes from 'pending'
while True:
    request_task = client.get(request.href)
    print("REST API Request Task = {}".format(request_task))
    if request_task['request_state'] != 'pending':
        print("Request task active")
        break
    sleep(5)

# We know (by reading the CF Automate code) that CloudForms' state machine
# checks active request's status in 60s after the request_status becomes
# 'active'. We wait for 60s, too.
sys.stdout.write('Waiting for 60 seconds ...')
sys.stdout.flush()
sleep(60)
print

# Poll one in 5s til the request_status becomes 'finished'.
예제 #6
0
class ManageIQAlert(object):
    """ ManageIQ object to execute alert definitions management operations in manageiq

    url            - manageiq environment url
    user           - the username in manageiq
    password       - the user password in manageiq
    miq_verify_ssl - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """

    supported_entities = {
        'container_node': 'ContainerNode',
        'vm': 'Vm',
        'miq_server': 'MiqServer',
        'host': 'Host',
        'storage': 'Storage',
        'cluster': 'EmsCluster',
        'ems': 'ExtManagementSystem',
        'miq_server': 'MiqServer',
        'middleware_server': 'MiddlewareServer'
    }

    def __init__(self, module, url, user, password, miq_verify_ssl,
                 ca_bundle_path):
        self.module = module
        self.api_url = url + '/api'
        self.user = user
        self.password = password
        self.client = MiqApi(self.api_url, (self.user, self.password),
                             verify_ssl=miq_verify_ssl,
                             ca_bundle_path=ca_bundle_path)
        self.changed = False

    def find_alert_by_description(self, description):
        """ Searches the alert description in ManageIQ.

        Returns:
            the alert id if it exists in manageiq, None otherwise.
        """
        try:
            response = self.client.get(
                '{api_url}/alert_definitions?expand=resources'.format(
                    api_url=self.api_url))
        except Exception as e:
            self.module.fail_json(msg="Failed to query alerts: {error}".format(
                error=e))
        alerts = response.get('resources', [])
        return next(
            (alert['id']
             for alert in alerts if alert['description'] == description), None)

    def delete_alert(self, description):
        """Deletes the alert from manageiq.

        Returns:
            a short message describing the operation executed.
        """
        alert_id = self.find_alert_by_description(description)
        if not alert_id:  # alert doesn't exist
            return dict(
                changed=self.changed,
                msg="Alert {description} does not exist in manageiq".format(
                    description=description))
        try:
            url = '{api_url}/alert_definitions/{alert_id}'.format(
                api_url=self.api_url, alert_id=alert_id)
            result = self.client.post(url, action='delete')
        except Exception as e:
            self.module.fail_json(
                msg="Failed to delete alert {description}: {error}".format(
                    description=description, error=e))
        self.changed = True
        return dict(changed=self.changed, msg=result['message'])

    def alert_update_required(self, alert_id, description, expression,
                              expression_type, miq_entity, options, enabled):
        """ Returns true if the expression, miq_entity, options, or enabled passed for
            the alert differ from the alert's existing ones, False otherwise.
        """
        url = "{api_url}/alert_definitions/{alert_id}".format(
            api_url=self.api_url, alert_id=alert_id)
        try:
            result = self.client.get(url)
        except Exception as e:
            self.module.fail_json(
                msg="Failed to get alert {description} details. Error: {error}"
                .format(description=description, error=e))

        # remove None values from expression and options dicts, if needed
        # TODO (dkorn): use the expression_type from the response, once supported
        if expression_type == 'miq_expression':
            current_expression = {
                k: v
                for k, v in result['expression']['exp'].items()
                if v is not None
            }
        else:
            current_expression = result['expression']
        current_options = {
            k: v
            for k, v in result['options'].items() if v is not None
        }

        attributes_tuples = [(current_expression, expression),
                             (result['db'], miq_entity),
                             (current_options, options),
                             (result['enabled'], enabled)]
        for (current, desired) in attributes_tuples:
            if desired is not None and current != desired:
                return True
        return False

    def update_alert_if_required(self, alert_id, description, expression,
                                 expression_type, miq_entity, options,
                                 enabled):
        """Updates the alert in manageiq.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        if not self.alert_update_required(alert_id, description, expression,
                                          expression_type, miq_entity, options,
                                          enabled):
            return dict(
                changed=self.changed,
                msg="Alert {description} already exist, no need for updates".
                format(description=description))

        url = '{api_url}/alert_definitions/{alert_id}'.format(
            api_url=self.api_url, alert_id=alert_id)
        resource = {
            'description': description,
            'expression': expression,
            'expression_type': expression_type,
            'db': miq_entity,
            'options': options,
            'enabled': enabled
        }
        try:
            result = self.client.post(url, action='edit', resource=resource)
        except Exception as e:
            self.module.fail_json(
                msg="Failed to update alert {description}: {error}".format(
                    description=description, error=e))
        self.changed = True
        return dict(
            changed=self.changed,
            msg="Successfully updated alert {description}: {alert_details}".
            format(description=description, alert_details=result))

    def create_alert(self, description, expression, expression_type,
                     miq_entity, options, enabled):
        """Creates the alert in manageiq.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        url = '{api_url}/alert_definitions/'.format(api_url=self.api_url)
        resource = {
            'description': description,
            'expression': expression,
            'expression_type': expression_type,
            'db': miq_entity,
            'options': options,
            'enabled': enabled
        }
        try:
            result = self.client.post(url, action='create', resource=resource)
            self.changed = True
            return dict(
                changed=self.changed,
                msg="Successfully created alert {description}: {alert_details}"
                .format(description=description,
                        alert_details=result['results']))
        except Exception as e:
            self.module.fail_json(
                msg="Failed to create alert {description}: {error}".format(
                    description=description, error=e))

    def create_or_update_alert(self, description, expression, expression_type,
                               entity, options, enabled):
        """ Create or update an alert in manageiq.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        miq_entity = ManageIQAlert.supported_entities[entity]
        alert_id = self.find_alert_by_description(description)
        if alert_id:  # alert already exist
            return self.update_alert_if_required(alert_id, description,
                                                 expression, expression_type,
                                                 miq_entity, options, enabled)
        else:
            return self.create_alert(description, expression, expression_type,
                                     miq_entity, options, enabled)
예제 #7
0
class ManageIQ(object):
    """ ManageIQ object to execute policy assignments in manageiq

    url      - manageiq environment url
    user     - the username in manageiq
    password - the user password in manageiq
    verify_ssl     - whether SSL certificates should be verified for HTTPS requests
    ca_bundle_path - the path to a CA_BUNDLE file or directory with certificates
    """

    manageiq_entities = {
        'policy': 'policies', 'policy profile': 'policy_profiles',
        'provider': 'providers', 'host': 'hosts', 'vm': 'vms',
        'container node': 'container_nodes', 'pod': 'container_groups',
        'replicator': 'container_replicators',
        'container image': 'container_images'
    }
    policy_actions = {
        'present': 'assign', 'absent': 'unassign'
    }

    def __init__(self, module, url, user, password, verify_ssl, ca_bundle_path):
        self.module        = module
        self.api_url       = url + '/api'
        self.user          = user
        self.password      = password
        self.client        = MiqApi(self.api_url, (self.user, self.password), verify_ssl=verify_ssl, ca_bundle_path=ca_bundle_path)
        self.changed       = False

    def find_entity_by_name(self, entity_type, entity_name):
        """ Searches the entity name in ManageIQ.

        Returns:
            the entity id if it exists in manageiq, None otherwise.
        """
        entities_list = getattr(self.client.collections, entity_type)
        return next((e.id for e in entities_list if e.name == entity_name), None)

    def query_resource_policies_or_profiles(self, entity_type, resource_type, resource_id):
        """ Returns the policies or policy profiles assigned to the resource.
        """
        try:
            url = '{api_url}/{resource_type}/{resource_id}/{entity_type}?expand=resources'.format(api_url=self.api_url, resource_type=resource_type, resource_id=resource_id, entity_type=entity_type)
            result = self.client.get(url)
            return result.get('resources', [])
        except Exception as e:
            self.module.fail_json(msg="Failed to query resource {entity_type}: {error}".format(entity_type=entity_type, error=e))

    def entity_assigned(self, entity_type, entity_id, resource_type, resource_id):
        """Return True if the action is needed on the resource, False otherwise.
        """
        assigned_entities = self.query_resource_policies_or_profiles(entity_type, resource_type, resource_id)
        return any(ae['id'] == entity_id for ae in assigned_entities)

    def execute_action(self, entity_type, entity_id, resource_type, resource_id, action):
        """Executes the action for the relevant entity on the resource.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        try:
            href = '{api_url}/{entity_type}/{entity_id}'.format(api_url=self.api_url, entity_type=entity_type, entity_id=entity_id)
            url = '{api_url}/{resource_type}/{resource_id}/{entity_type}'.format(api_url=self.api_url, resource_type=resource_type, resource_id=resource_id, entity_type=entity_type)
            result = self.client.post(url, action=action, resource={'href': href})
            if result['results'][0]['success']:
                self.changed = True
                return dict(
                    changed=self.changed,
                    msg=result['results'][0]['message']
                )
            else:
                self.module.fail_json(msg="Failed to {action}: {fail_message}".format(action=action, entity=entity, fail_message=result['results'][0]['message']))
        except Exception as e:
            self.module.fail_json(msg="Failed to {action}: {error}".format(action=action, entity=entity, error=e))

    def assign_or_unassign_entity(self, entity, entity_name, resource, resource_name, state):
        """ Assign or unassign the entity on the manageiq resource.

        Returns:
            Whether or not a change took place and a message describing the
            operation executed.
        """
        entity_type = self.manageiq_entities[entity]
        resource_type = self.manageiq_entities[resource]
        entity_id = self.find_entity_by_name(entity_type, entity_name)
        if not entity_id:  # entity doesn't exist
            self.module.fail_json(
                msg="Failed to {action} {entity}: {entity_name} does not exist in manageiq".format(action=ManageIQ.policy_actions[state], entity=entity, entity_name=entity_name))

        resource_id = self.find_entity_by_name(resource_type, resource_name)
        if not resource_id:  # resource doesn't exist
            self.module.fail_json(
                msg="Failed to {action} {entity}: {resource_name} {resource} does not exist in manageiq".format(action=ManageIQ.policy_actions[state], entity=entity, resource_name=resource_name, resource=resource))

        assigned = self.entity_assigned(entity_type, entity_id, resource_type, resource_id)
        if assigned and state == 'absent':
            return self.execute_action(entity_type, entity_id, resource_type, resource_id, 'unassign')
        if (not assigned) and state == 'present':
            return self.execute_action(entity_type, entity_id, resource_type, resource_id, 'assign')

        #  Default case is that there's nothing to change
        return dict(
            changed=self.changed,
            msg="{entity_name} {entity} already {action}ed".format(entity_name=entity_name, entity=entity, action=ManageIQ.policy_actions[state]))