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