1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# Copyright (c) 2017 Red Hat Inc. 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import (absolute_import, division, print_function) 7__metaclass__ = type 8 9 10DOCUMENTATION = ''' 11 12module: manageiq_alert_profiles 13 14short_description: Configuration of alert profiles for ManageIQ 15extends_documentation_fragment: 16- community.general.manageiq 17 18author: Elad Alfassa (@elad661) <ealfassa@redhat.com> 19description: 20 - The manageiq_alert_profiles module supports adding, updating and deleting alert profiles in ManageIQ. 21 22options: 23 state: 24 type: str 25 description: 26 - absent - alert profile should not exist, 27 - present - alert profile should exist, 28 choices: ['absent', 'present'] 29 default: 'present' 30 name: 31 type: str 32 description: 33 - The unique alert profile name in ManageIQ. 34 - Required when state is "absent" or "present". 35 resource_type: 36 type: str 37 description: 38 - The resource type for the alert profile in ManageIQ. Required when state is "present". 39 choices: ['Vm', 'ContainerNode', 'MiqServer', 'Host', 'Storage', 'EmsCluster', 40 'ExtManagementSystem', 'MiddlewareServer'] 41 alerts: 42 type: list 43 elements: str 44 description: 45 - List of alert descriptions to assign to this profile. 46 - Required if state is "present" 47 notes: 48 type: str 49 description: 50 - Optional notes for this profile 51 52''' 53 54EXAMPLES = ''' 55- name: Add an alert profile to ManageIQ 56 community.general.manageiq_alert_profiles: 57 state: present 58 name: Test profile 59 resource_type: ContainerNode 60 alerts: 61 - Test Alert 01 62 - Test Alert 02 63 manageiq_connection: 64 url: 'http://127.0.0.1:3000' 65 username: 'admin' 66 password: 'smartvm' 67 validate_certs: False 68 69- name: Delete an alert profile from ManageIQ 70 community.general.manageiq_alert_profiles: 71 state: absent 72 name: Test profile 73 manageiq_connection: 74 url: 'http://127.0.0.1:3000' 75 username: 'admin' 76 password: 'smartvm' 77 validate_certs: False 78''' 79 80RETURN = ''' 81''' 82 83from ansible.module_utils.basic import AnsibleModule 84from ansible_collections.community.general.plugins.module_utils.manageiq import ManageIQ, manageiq_argument_spec 85 86 87class ManageIQAlertProfiles(object): 88 """ Object to execute alert profile management operations in manageiq. 89 """ 90 91 def __init__(self, manageiq): 92 self.manageiq = manageiq 93 94 self.module = self.manageiq.module 95 self.api_url = self.manageiq.api_url 96 self.client = self.manageiq.client 97 self.url = '{api_url}/alert_definition_profiles'.format(api_url=self.api_url) 98 99 def get_profiles(self): 100 """ Get all alert profiles from ManageIQ 101 """ 102 try: 103 response = self.client.get(self.url + '?expand=alert_definitions,resources') 104 except Exception as e: 105 self.module.fail_json(msg="Failed to query alert profiles: {error}".format(error=e)) 106 return response.get('resources') or [] 107 108 def get_alerts(self, alert_descriptions): 109 """ Get a list of alert hrefs from a list of alert descriptions 110 """ 111 alerts = [] 112 for alert_description in alert_descriptions: 113 alert = self.manageiq.find_collection_resource_or_fail("alert_definitions", 114 description=alert_description) 115 alerts.append(alert['href']) 116 117 return alerts 118 119 def add_profile(self, profile): 120 """ Add a new alert profile to ManageIQ 121 """ 122 # find all alerts to add to the profile 123 # we do this first to fail early if one is missing. 124 alerts = self.get_alerts(profile['alerts']) 125 126 # build the profile dict to send to the server 127 128 profile_dict = dict(name=profile['name'], 129 description=profile['name'], 130 mode=profile['resource_type']) 131 if profile['notes']: 132 profile_dict['set_data'] = dict(notes=profile['notes']) 133 134 # send it to the server 135 try: 136 result = self.client.post(self.url, resource=profile_dict, action="create") 137 except Exception as e: 138 self.module.fail_json(msg="Creating profile failed {error}".format(error=e)) 139 140 # now that it has been created, we can assign the alerts 141 self.assign_or_unassign(result['results'][0], alerts, "assign") 142 143 msg = "Profile {name} created successfully" 144 msg = msg.format(name=profile['name']) 145 return dict(changed=True, msg=msg) 146 147 def delete_profile(self, profile): 148 """ Delete an alert profile from ManageIQ 149 """ 150 try: 151 self.client.post(profile['href'], action="delete") 152 except Exception as e: 153 self.module.fail_json(msg="Deleting profile failed: {error}".format(error=e)) 154 155 msg = "Successfully deleted profile {name}".format(name=profile['name']) 156 return dict(changed=True, msg=msg) 157 158 def get_alert_href(self, alert): 159 """ Get an absolute href for an alert 160 """ 161 return "{url}/alert_definitions/{id}".format(url=self.api_url, id=alert['id']) 162 163 def assign_or_unassign(self, profile, resources, action): 164 """ Assign or unassign alerts to profile, and validate the result. 165 """ 166 alerts = [dict(href=href) for href in resources] 167 168 subcollection_url = profile['href'] + '/alert_definitions' 169 try: 170 result = self.client.post(subcollection_url, resources=alerts, action=action) 171 if len(result['results']) != len(alerts): 172 msg = "Failed to {action} alerts to profile '{name}'," +\ 173 "expected {expected} alerts to be {action}ed," +\ 174 "but only {changed} were {action}ed" 175 msg = msg.format(action=action, 176 name=profile['name'], 177 expected=len(alerts), 178 changed=result['results']) 179 self.module.fail_json(msg=msg) 180 except Exception as e: 181 msg = "Failed to {action} alerts to profile '{name}': {error}" 182 msg = msg.format(action=action, name=profile['name'], error=e) 183 self.module.fail_json(msg=msg) 184 185 return result['results'] 186 187 def update_profile(self, old_profile, desired_profile): 188 """ Update alert profile in ManageIQ 189 """ 190 changed = False 191 # we need to use client.get to query the alert definitions 192 old_profile = self.client.get(old_profile['href'] + '?expand=alert_definitions') 193 194 # figure out which alerts we need to assign / unassign 195 # alerts listed by the user: 196 desired_alerts = set(self.get_alerts(desired_profile['alerts'])) 197 198 # alert which currently exist in the profile 199 if 'alert_definitions' in old_profile: 200 # we use get_alert_href to have a direct href to the alert 201 existing_alerts = set([self.get_alert_href(alert) for alert in old_profile['alert_definitions']]) 202 else: 203 # no alerts in this profile 204 existing_alerts = set() 205 206 to_add = list(desired_alerts - existing_alerts) 207 to_remove = list(existing_alerts - desired_alerts) 208 209 # assign / unassign the alerts, if needed 210 211 if to_remove: 212 self.assign_or_unassign(old_profile, to_remove, "unassign") 213 changed = True 214 if to_add: 215 self.assign_or_unassign(old_profile, to_add, "assign") 216 changed = True 217 218 # update other properties 219 profile_dict = dict() 220 221 if old_profile['mode'] != desired_profile['resource_type']: 222 # mode needs to be updated 223 profile_dict['mode'] = desired_profile['resource_type'] 224 225 # check if notes need to be updated 226 old_notes = old_profile.get('set_data', {}).get('notes') 227 228 if desired_profile['notes'] != old_notes: 229 profile_dict['set_data'] = dict(notes=desired_profile['notes']) 230 231 if profile_dict: 232 # if we have any updated values 233 changed = True 234 try: 235 result = self.client.post(old_profile['href'], 236 resource=profile_dict, 237 action="edit") 238 except Exception as e: 239 msg = "Updating profile '{name}' failed: {error}" 240 msg = msg.format(name=old_profile['name'], error=e) 241 self.module.fail_json(msg=msg, result=result) 242 243 if changed: 244 msg = "Profile {name} updated successfully".format(name=desired_profile['name']) 245 else: 246 msg = "No update needed for profile {name}".format(name=desired_profile['name']) 247 return dict(changed=changed, msg=msg) 248 249 250def main(): 251 argument_spec = dict( 252 name=dict(type='str'), 253 resource_type=dict(type='str', choices=['Vm', 254 'ContainerNode', 255 'MiqServer', 256 'Host', 257 'Storage', 258 'EmsCluster', 259 'ExtManagementSystem', 260 'MiddlewareServer']), 261 alerts=dict(type='list', elements='str'), 262 notes=dict(type='str'), 263 state=dict(default='present', choices=['present', 'absent']), 264 ) 265 # add the manageiq connection arguments to the arguments 266 argument_spec.update(manageiq_argument_spec()) 267 268 module = AnsibleModule(argument_spec=argument_spec, 269 required_if=[('state', 'present', ['name', 'resource_type']), 270 ('state', 'absent', ['name'])]) 271 272 state = module.params['state'] 273 name = module.params['name'] 274 275 manageiq = ManageIQ(module) 276 manageiq_alert_profiles = ManageIQAlertProfiles(manageiq) 277 278 existing_profile = manageiq.find_collection_resource_by("alert_definition_profiles", 279 name=name) 280 281 # we need to add or update the alert profile 282 if state == "present": 283 if not existing_profile: 284 # a profile with this name doesn't exist yet, let's create it 285 res_args = manageiq_alert_profiles.add_profile(module.params) 286 else: 287 # a profile with this name exists, we might need to update it 288 res_args = manageiq_alert_profiles.update_profile(existing_profile, module.params) 289 290 # this alert profile should not exist 291 if state == "absent": 292 # if we have an alert profile with this name, delete it 293 if existing_profile: 294 res_args = manageiq_alert_profiles.delete_profile(existing_profile) 295 else: 296 # This alert profile does not exist in ManageIQ, and that's okay 297 msg = "Alert profile '{name}' does not exist in ManageIQ" 298 msg = msg.format(name=name) 299 res_args = dict(changed=False, msg=msg) 300 301 module.exit_json(**res_args) 302 303 304if __name__ == "__main__": 305 main() 306