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