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]))
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)
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)
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'.
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)
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]))