#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2017 Red Hat Inc. # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import (absolute_import, division, print_function) __metaclass__ = type DOCUMENTATION = ''' module: manageiq_alert_profiles short_description: Configuration of alert profiles for ManageIQ extends_documentation_fragment: - community.general.manageiq - community.general.attributes author: Elad Alfassa (@elad661) description: - The manageiq_alert_profiles module supports adding, updating and deleting alert profiles in ManageIQ. attributes: check_mode: support: none diff_mode: support: none options: state: type: str description: - absent - alert profile should not exist, - present - alert profile should exist, choices: ['absent', 'present'] default: 'present' name: type: str description: - The unique alert profile name in ManageIQ. - Required when state is "absent" or "present". resource_type: type: str description: - The resource type for the alert profile in ManageIQ. Required when state is "present". choices: ['Vm', 'ContainerNode', 'MiqServer', 'Host', 'Storage', 'EmsCluster', 'ExtManagementSystem', 'MiddlewareServer'] alerts: type: list elements: str description: - List of alert descriptions to assign to this profile. - Required if state is "present" notes: type: str description: - Optional notes for this profile ''' EXAMPLES = ''' - name: Add an alert profile to ManageIQ community.general.manageiq_alert_profiles: state: present name: Test profile resource_type: ContainerNode alerts: - Test Alert 01 - Test Alert 02 manageiq_connection: url: 'http://127.0.0.1:3000' username: 'admin' password: 'smartvm' validate_certs: false # only do this when you trust the network! - name: Delete an alert profile from ManageIQ community.general.manageiq_alert_profiles: state: absent name: Test profile manageiq_connection: url: 'http://127.0.0.1:3000' username: 'admin' password: 'smartvm' validate_certs: false # only do this when you trust the network! ''' RETURN = ''' ''' from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.manageiq import ManageIQ, manageiq_argument_spec class ManageIQAlertProfiles(object): """ Object to execute alert profile management operations in manageiq. """ def __init__(self, manageiq): self.manageiq = manageiq self.module = self.manageiq.module self.api_url = self.manageiq.api_url self.client = self.manageiq.client self.url = '{api_url}/alert_definition_profiles'.format(api_url=self.api_url) def get_profiles(self): """ Get all alert profiles from ManageIQ """ try: response = self.client.get(self.url + '?expand=alert_definitions,resources') except Exception as e: self.module.fail_json(msg="Failed to query alert profiles: {error}".format(error=e)) return response.get('resources') or [] def get_alerts(self, alert_descriptions): """ Get a list of alert hrefs from a list of alert descriptions """ alerts = [] for alert_description in alert_descriptions: alert = self.manageiq.find_collection_resource_or_fail("alert_definitions", description=alert_description) alerts.append(alert['href']) return alerts def add_profile(self, profile): """ Add a new alert profile to ManageIQ """ # find all alerts to add to the profile # we do this first to fail early if one is missing. alerts = self.get_alerts(profile['alerts']) # build the profile dict to send to the server profile_dict = dict(name=profile['name'], description=profile['name'], mode=profile['resource_type']) if profile['notes']: profile_dict['set_data'] = dict(notes=profile['notes']) # send it to the server try: result = self.client.post(self.url, resource=profile_dict, action="create") except Exception as e: self.module.fail_json(msg="Creating profile failed {error}".format(error=e)) # now that it has been created, we can assign the alerts self.assign_or_unassign(result['results'][0], alerts, "assign") msg = "Profile {name} created successfully" msg = msg.format(name=profile['name']) return dict(changed=True, msg=msg) def delete_profile(self, profile): """ Delete an alert profile from ManageIQ """ try: self.client.post(profile['href'], action="delete") except Exception as e: self.module.fail_json(msg="Deleting profile failed: {error}".format(error=e)) msg = "Successfully deleted profile {name}".format(name=profile['name']) return dict(changed=True, msg=msg) def get_alert_href(self, alert): """ Get an absolute href for an alert """ return "{url}/alert_definitions/{id}".format(url=self.api_url, id=alert['id']) def assign_or_unassign(self, profile, resources, action): """ Assign or unassign alerts to profile, and validate the result. """ alerts = [dict(href=href) for href in resources] subcollection_url = profile['href'] + '/alert_definitions' try: result = self.client.post(subcollection_url, resources=alerts, action=action) if len(result['results']) != len(alerts): msg = "Failed to {action} alerts to profile '{name}'," +\ "expected {expected} alerts to be {action}ed," +\ "but only {changed} were {action}ed" msg = msg.format(action=action, name=profile['name'], expected=len(alerts), changed=result['results']) self.module.fail_json(msg=msg) except Exception as e: msg = "Failed to {action} alerts to profile '{name}': {error}" msg = msg.format(action=action, name=profile['name'], error=e) self.module.fail_json(msg=msg) return result['results'] def update_profile(self, old_profile, desired_profile): """ Update alert profile in ManageIQ """ changed = False # we need to use client.get to query the alert definitions old_profile = self.client.get(old_profile['href'] + '?expand=alert_definitions') # figure out which alerts we need to assign / unassign # alerts listed by the user: desired_alerts = set(self.get_alerts(desired_profile['alerts'])) # alert which currently exist in the profile if 'alert_definitions' in old_profile: # we use get_alert_href to have a direct href to the alert existing_alerts = set([self.get_alert_href(alert) for alert in old_profile['alert_definitions']]) else: # no alerts in this profile existing_alerts = set() to_add = list(desired_alerts - existing_alerts) to_remove = list(existing_alerts - desired_alerts) # assign / unassign the alerts, if needed if to_remove: self.assign_or_unassign(old_profile, to_remove, "unassign") changed = True if to_add: self.assign_or_unassign(old_profile, to_add, "assign") changed = True # update other properties profile_dict = dict() if old_profile['mode'] != desired_profile['resource_type']: # mode needs to be updated profile_dict['mode'] = desired_profile['resource_type'] # check if notes need to be updated old_notes = old_profile.get('set_data', {}).get('notes') if desired_profile['notes'] != old_notes: profile_dict['set_data'] = dict(notes=desired_profile['notes']) if profile_dict: # if we have any updated values changed = True try: result = self.client.post(old_profile['href'], resource=profile_dict, action="edit") except Exception as e: msg = "Updating profile '{name}' failed: {error}" msg = msg.format(name=old_profile['name'], error=e) self.module.fail_json(msg=msg) if changed: msg = "Profile {name} updated successfully".format(name=desired_profile['name']) else: msg = "No update needed for profile {name}".format(name=desired_profile['name']) return dict(changed=changed, msg=msg) def main(): argument_spec = dict( name=dict(type='str'), resource_type=dict(type='str', choices=['Vm', 'ContainerNode', 'MiqServer', 'Host', 'Storage', 'EmsCluster', 'ExtManagementSystem', 'MiddlewareServer']), alerts=dict(type='list', elements='str'), notes=dict(type='str'), state=dict(default='present', choices=['present', 'absent']), ) # add the manageiq connection arguments to the arguments argument_spec.update(manageiq_argument_spec()) module = AnsibleModule(argument_spec=argument_spec, required_if=[('state', 'present', ['name', 'resource_type']), ('state', 'absent', ['name'])]) state = module.params['state'] name = module.params['name'] manageiq = ManageIQ(module) manageiq_alert_profiles = ManageIQAlertProfiles(manageiq) existing_profile = manageiq.find_collection_resource_by("alert_definition_profiles", name=name) # we need to add or update the alert profile if state == "present": if not existing_profile: # a profile with this name doesn't exist yet, let's create it res_args = manageiq_alert_profiles.add_profile(module.params) else: # a profile with this name exists, we might need to update it res_args = manageiq_alert_profiles.update_profile(existing_profile, module.params) # this alert profile should not exist if state == "absent": # if we have an alert profile with this name, delete it if existing_profile: res_args = manageiq_alert_profiles.delete_profile(existing_profile) else: # This alert profile does not exist in ManageIQ, and that's okay msg = "Alert profile '{name}' does not exist in ManageIQ" msg = msg.format(name=name) res_args = dict(changed=False, msg=msg) module.exit_json(**res_args) if __name__ == "__main__": main()