1#!/usr/bin/python 2 3# (c) 2018, NetApp, 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 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = """ 15--- 16module: netapp_e_alerts 17short_description: NetApp E-Series manage email notification settings 18description: 19 - Certain E-Series systems have the capability to send email notifications on potentially critical events. 20 - This module will allow the owner of the system to specify email recipients for these messages. 21version_added: '2.7' 22author: Michael Price (@lmprice) 23extends_documentation_fragment: 24 - netapp.eseries 25options: 26 state: 27 description: 28 - Enable/disable the sending of email-based alerts. 29 default: enabled 30 required: false 31 choices: 32 - enabled 33 - disabled 34 server: 35 description: 36 - A fully qualified domain name, IPv4 address, or IPv6 address of a mail server. 37 - To use a fully qualified domain name, you must configure a DNS server on both controllers using 38 M(netapp_e_mgmt_interface). 39 - Required when I(state=enabled). 40 required: no 41 sender: 42 description: 43 - This is the sender that the recipient will see. It doesn't necessarily need to be a valid email account. 44 - Required when I(state=enabled). 45 required: no 46 contact: 47 description: 48 - Allows the owner to specify some free-form contact information to be included in the emails. 49 - This is typically utilized to provide a contact phone number. 50 required: no 51 recipients: 52 description: 53 - The email addresses that will receive the email notifications. 54 - Required when I(state=enabled). 55 required: no 56 test: 57 description: 58 - When a change is detected in the configuration, a test email will be sent. 59 - This may take a few minutes to process. 60 - Only applicable if I(state=enabled). 61 default: no 62 type: bool 63 log_path: 64 description: 65 - Path to a file on the Ansible control node to be used for debug logging 66 required: no 67notes: 68 - Check mode is supported. 69 - Alertable messages are a subset of messages shown by the Major Event Log (MEL), of the storage-system. Examples 70 of alertable messages include drive failures, failed controllers, loss of redundancy, and other warning/critical 71 events. 72 - This API is currently only supported with the Embedded Web Services API v2.0 and higher. 73""" 74 75EXAMPLES = """ 76 - name: Enable email-based alerting 77 netapp_e_alerts: 78 state: enabled 79 sender: noreply@example.com 80 server: mail@example.com 81 contact: "Phone: 1-555-555-5555" 82 recipients: 83 - name1@example.com 84 - name2@example.com 85 api_url: "10.1.1.1:8443" 86 api_username: "admin" 87 api_password: "myPass" 88 89 - name: Disable alerting 90 netapp_e_alerts: 91 state: disabled 92 api_url: "10.1.1.1:8443" 93 api_username: "admin" 94 api_password: "myPass" 95""" 96 97RETURN = """ 98msg: 99 description: Success message 100 returned: on success 101 type: str 102 sample: The settings have been updated. 103""" 104 105import json 106import logging 107from pprint import pformat 108import re 109 110from ansible.module_utils.basic import AnsibleModule 111from ansible.module_utils.netapp import request, eseries_host_argument_spec 112from ansible.module_utils._text import to_native 113 114HEADERS = { 115 "Content-Type": "application/json", 116 "Accept": "application/json", 117} 118 119 120class Alerts(object): 121 def __init__(self): 122 argument_spec = eseries_host_argument_spec() 123 argument_spec.update(dict( 124 state=dict(type='str', required=False, default='enabled', 125 choices=['enabled', 'disabled']), 126 server=dict(type='str', required=False, ), 127 sender=dict(type='str', required=False, ), 128 contact=dict(type='str', required=False, ), 129 recipients=dict(type='list', required=False, ), 130 test=dict(type='bool', required=False, default=False, ), 131 log_path=dict(type='str', required=False), 132 )) 133 134 required_if = [ 135 ['state', 'enabled', ['server', 'sender', 'recipients']] 136 ] 137 138 self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) 139 args = self.module.params 140 self.alerts = args['state'] == 'enabled' 141 self.server = args['server'] 142 self.sender = args['sender'] 143 self.contact = args['contact'] 144 self.recipients = args['recipients'] 145 self.test = args['test'] 146 147 self.ssid = args['ssid'] 148 self.url = args['api_url'] 149 self.creds = dict(url_password=args['api_password'], 150 validate_certs=args['validate_certs'], 151 url_username=args['api_username'], ) 152 153 self.check_mode = self.module.check_mode 154 155 log_path = args['log_path'] 156 157 # logging setup 158 self._logger = logging.getLogger(self.__class__.__name__) 159 160 if log_path: 161 logging.basicConfig( 162 level=logging.DEBUG, filename=log_path, filemode='w', 163 format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s') 164 165 if not self.url.endswith('/'): 166 self.url += '/' 167 168 # Very basic validation on email addresses: xx@yy.zz 169 email = re.compile(r"[^@]+@[^@]+\.[^@]+") 170 171 if self.sender and not email.match(self.sender): 172 self.module.fail_json(msg="The sender (%s) provided is not a valid email address." % self.sender) 173 174 if self.recipients is not None: 175 for recipient in self.recipients: 176 if not email.match(recipient): 177 self.module.fail_json(msg="The recipient (%s) provided is not a valid email address." % recipient) 178 179 if len(self.recipients) < 1: 180 self.module.fail_json(msg="At least one recipient address must be specified.") 181 182 def get_configuration(self): 183 try: 184 (rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, headers=HEADERS, 185 **self.creds) 186 self._logger.info("Current config: %s", pformat(result)) 187 return result 188 189 except Exception as err: 190 self.module.fail_json(msg="Failed to retrieve the alerts configuration! Array Id [%s]. Error [%s]." 191 % (self.ssid, to_native(err))) 192 193 def update_configuration(self): 194 config = self.get_configuration() 195 update = False 196 body = dict() 197 198 if self.alerts: 199 body = dict(alertingEnabled=True) 200 if not config['alertingEnabled']: 201 update = True 202 203 body.update(emailServerAddress=self.server) 204 if config['emailServerAddress'] != self.server: 205 update = True 206 207 body.update(additionalContactInformation=self.contact, sendAdditionalContactInformation=True) 208 if self.contact and (self.contact != config['additionalContactInformation'] 209 or not config['sendAdditionalContactInformation']): 210 update = True 211 212 body.update(emailSenderAddress=self.sender) 213 if config['emailSenderAddress'] != self.sender: 214 update = True 215 216 self.recipients.sort() 217 if config['recipientEmailAddresses']: 218 config['recipientEmailAddresses'].sort() 219 220 body.update(recipientEmailAddresses=self.recipients) 221 if config['recipientEmailAddresses'] != self.recipients: 222 update = True 223 224 elif config['alertingEnabled']: 225 body = dict(alertingEnabled=False) 226 update = True 227 228 self._logger.debug(pformat(body)) 229 230 if update and not self.check_mode: 231 try: 232 (rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, method='POST', 233 data=json.dumps(body), headers=HEADERS, **self.creds) 234 # This is going to catch cases like a connection failure 235 except Exception as err: 236 self.module.fail_json(msg="We failed to set the storage-system name! Array Id [%s]. Error [%s]." 237 % (self.ssid, to_native(err))) 238 return update 239 240 def send_test_email(self): 241 """Send a test email to verify that the provided configuration is valid and functional.""" 242 if not self.check_mode: 243 try: 244 (rc, result) = request(self.url + 'storage-systems/%s/device-alerts/alert-email-test' % self.ssid, 245 timeout=300, method='POST', headers=HEADERS, **self.creds) 246 247 if result['response'] != 'emailSentOK': 248 self.module.fail_json(msg="The test email failed with status=[%s]! Array Id [%s]." 249 % (result['response'], self.ssid)) 250 251 # This is going to catch cases like a connection failure 252 except Exception as err: 253 self.module.fail_json(msg="We failed to send the test email! Array Id [%s]. Error [%s]." 254 % (self.ssid, to_native(err))) 255 256 def update(self): 257 update = self.update_configuration() 258 259 if self.test and update: 260 self._logger.info("An update was detected and test=True, running a test.") 261 self.send_test_email() 262 263 if self.alerts: 264 msg = 'Alerting has been enabled using server=%s, sender=%s.' % (self.server, self.sender) 265 else: 266 msg = 'Alerting has been disabled.' 267 268 self.module.exit_json(msg=msg, changed=update, ) 269 270 def __call__(self, *args, **kwargs): 271 self.update() 272 273 274def main(): 275 alerts = Alerts() 276 alerts() 277 278 279if __name__ == '__main__': 280 main() 281