From 79946c5f439edb9f2f8168a6d19e1541204a171d Mon Sep 17 00:00:00 2001 From: Benjamin Copeland Date: Mon, 20 Jun 2016 17:50:23 +0100 Subject: [PATCH] Adding statusio_maintenance module (#1394) --- .../extras/monitoring/statusio_maintenance.py | 478 ++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 lib/ansible/modules/extras/monitoring/statusio_maintenance.py diff --git a/lib/ansible/modules/extras/monitoring/statusio_maintenance.py b/lib/ansible/modules/extras/monitoring/statusio_maintenance.py new file mode 100644 index 00000000000..893545e4057 --- /dev/null +++ b/lib/ansible/modules/extras/monitoring/statusio_maintenance.py @@ -0,0 +1,478 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Benjamin Copeland (@bhcopeland) +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +DOCUMENTATION = ''' + +module: statusio_maintenance +short_description: Create maintenance windows for your status.io dashboard +description: + - Creates a maintenance window for status.io + - Deletes a maintenance window for status.io +notes: + - You can use the apiary API url (http://docs.statusio.apiary.io/) to + capture API traffic + - Use start_date and start_time with minutes to set future maintenance window +version_added: "2.2" +author: Benjamin Copeland (@bhcopeland) +options: + title: + description: + - A descriptive title for the maintenance window + required: false + default: "A new maintenance window" + desc: + description: + - Message describing the maintenance window + required: false + default: "Created by Ansible" + state: + description: + - Desired state of the package. + required: false + default: "present" + choices: ["present", "absent"] + api_id: + description: + - Your unique API ID from status.io + required: true + api_key: + description: + - Your unique API Key from status.io + required: true + statuspage: + description: + - Your unique StatusPage ID from status.io + required: true + url: + description: + - Status.io API URL. A private apiary can be used instead. + required: false + default: "https://api.status.io" + components: + description: + - The given name of your component (server name) + required: false + aliases: ['component'] + default: None + containers: + description: + - The given name of your container (data center) + required: false + aliases: ['container'] + default: None + all_infrastructure_affected: + description: + - If it affects all components and containers + required: false + default: false + automation: + description: + - Automatically start and end the maintenance window + required: false + default: false + maintenance_notify_now: + description: + - Notify subscribers now + required: false + default: false + maintenance_notify_72_hr: + description: + - Notify subscribers 72 hours before maintenance start time + required: false + default: false + maintenance_notify_24_hr: + description: + - Notify subscribers 24 hours before maintenance start time + required: false + default: false + maintenance_notify_1_hr: + description: + - Notify subscribers 1 hour before maintenance start time + required: false + default: false + maintenance_id: + description: + - The maintenance id number when deleting a maintenance window + required: false + default: None + minutes: + description: + - The length of time in UTC that the maintenance will run \ + (starting from playbook runtime) + required: false + default: 10 + start_date: + description: + - Date maintenance is expected to start (Month/Day/Year) (UTC) + - End Date is worked out from start_date + minutes + required: false + default: None + start_time: + description: + - Time maintenance is expected to start (Hour:Minutes) (UTC) + - End Time is worked out from start_time + minutes + required: false + default: None +''' + +EXAMPLES = ''' +# Create a maintenance window for 10 minutes on server1.example.com, with +automation to stop the maintenance. +- statusio_maintenance: + title: "Router Upgrade from ansible" + desc: "Performing a Router Upgrade" + components: "server1.example.com" + api_id: "api_id" + api_key: "api_key" + statuspage: "statuspage_id" + maintenance_notify_1_hr: true + automation: true + +# Create a maintenance window for 60 minutes on multiple hosts +- name: "Create maintenance window for server1 and server2" + local_action: + module: statusio_maintenance + title: "Routine maintenance" + desc: "Some security updates" + components: + - "server1.example.com + - "server2.example.com" + minutes: "60" + api_id: "api_id" + api_key: "api_key" + statuspage: "statuspage_id" + maintenance_notify_1_hr: true + automation: true + +# Create a future maintenance window for 24 hours to all hosts inside the +# Primary Data Center +- statusio_maintenance: + title: Data center downtime + desc: Performing a Upgrade to our data center + components: "Primary Data Center" + api_id: "api_id" + api_key: "api_key" + statuspage: "statuspage_id" + start_date: "01/01/2016" + start_time: "12:00" + minutes: 1440 + +# Delete a maintenance window +- statusio_maintenance: + title: "Remove a maintenance window" + maintenance_id: "561f90faf74bc94a4700087b" + statuspage: "statuspage_id" + api_id: "api_id" + api_key: "api_key" + state: absent + +''' +# TODO: Add RETURN documentation. +RETURN = ''' # ''' + +import datetime + + +def get_api_auth_headers(api_id, api_key, url, statuspage): + + headers = { + "x-api-id": api_id, + "x-api-key": api_key, + "Content-Type": "application/json" + } + + try: + response = open_url( + url + "/v2/component/list/" + statuspage, headers=headers) + data = json.loads(response.read()) + if data['status']['message'] == 'Authentication failed': + return 1, None, None, "Authentication failed: " \ + "Check api_id/api_key and statuspage id." + else: + auth_headers = headers + auth_content = data + except: + return 1, None, None, e + return 0, auth_headers, auth_content, None + + +def get_component_ids(auth_content, components): + host_ids = [] + lower_components = [x.lower() for x in components] + for result in auth_content["result"]: + if result['name'].lower() in lower_components: + data = { + "component_id": result["_id"], + "container_id": result["containers"][0]["_id"] + } + host_ids.append(data) + lower_components.remove(result['name'].lower()) + if len(lower_components): + # items not found in the api + return 1, None, lower_components + return 0, host_ids, None + + +def get_container_ids(auth_content, containers): + host_ids = [] + lower_containers = [x.lower() for x in containers] + for result in auth_content["result"]: + if result["containers"][0]["name"].lower() in lower_containers: + data = { + "component_id": result["_id"], + "container_id": result["containers"][0]["_id"] + } + host_ids.append(data) + lower_containers.remove(result["containers"][0]["name"].lower()) + + if len(lower_containers): + # items not found in the api + return 1, None, lower_containers + return 0, host_ids, None + + +def get_date_time(start_date, start_time, minutes): + returned_date = [] + if start_date and start_time: + try: + datetime.datetime.strptime(start_date, '%m/%d/%Y') + returned_date.append(start_date) + except (NameError, ValueError): + return 1, None, "Not a valid start_date format." + try: + datetime.datetime.strptime(start_time, '%H:%M') + returned_date.append(start_time) + except (NameError, ValueError): + return 1, None, "Not a valid start_time format." + try: + # Work out end date/time based on minutes + date_time_start = datetime.datetime.strptime( + start_time + start_date, '%H:%M%m/%d/%Y') + delta = date_time_start + datetime.timedelta(minutes=minutes) + returned_date.append(delta.strftime("%m/%d/%Y")) + returned_date.append(delta.strftime("%H:%M")) + except (NameError, ValueError): + return 1, None, "Couldn't work out a valid date" + else: + now = datetime.datetime.utcnow() + delta = now + datetime.timedelta(minutes=minutes) + # start_date + returned_date.append(now.strftime("%m/%d/%Y")) + returned_date.append(now.strftime("%H:%M")) + # end_date + returned_date.append(delta.strftime("%m/%d/%Y")) + returned_date.append(delta.strftime("%H:%M")) + return 0, returned_date, None + + +def create_maintenance(auth_headers, url, statuspage, host_ids, + all_infrastructure_affected, automation, title, desc, + returned_date, maintenance_notify_now, + maintenance_notify_72_hr, maintenance_notify_24_hr, + maintenance_notify_1_hr): + returned_dates = [[x] for x in returned_date] + component_id = [] + container_id = [] + for val in host_ids: + component_id.append(val['component_id']) + container_id.append(val['container_id']) + try: + values = json.dumps({ + "statuspage_id": statuspage, + "components": component_id, + "containers": container_id, + "all_infrastructure_affected": + str(int(all_infrastructure_affected)), + "automation": str(int(automation)), + "maintenance_name": title, + "maintenance_details": desc, + "date_planned_start": returned_dates[0], + "time_planned_start": returned_dates[1], + "date_planned_end": returned_dates[2], + "time_planned_end": returned_dates[3], + "maintenance_notify_now": str(int(maintenance_notify_now)), + "maintenance_notify_72_hr": str(int(maintenance_notify_72_hr)), + "maintenance_notify_24_hr": str(int(maintenance_notify_24_hr)), + "maintenance_notify_1_hr": str(int(maintenance_notify_1_hr)) + }) + response = open_url( + url + "/v2/maintenance/schedule", data=values, + headers=auth_headers) + data = json.loads(response.read()) + + if data["status"]["error"] == "yes": + return 1, None, data["status"]["message"] + except Exception, e: + return 1, None, str(e) + return 0, None, None + + +def delete_maintenance(auth_headers, url, statuspage, maintenance_id): + try: + values = json.dumps({ + "statuspage_id": statuspage, + "maintenance_id": maintenance_id, + }) + response = open_url( + url=url + "/v2/maintenance/delete", + data=values, + headers=auth_headers) + data = json.loads(response.read()) + if data["status"]["error"] == "yes": + return 1, None, "Invalid maintenance_id" + except Exception, e: + return 1, None, str(e) + return 0, None, None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + api_id=dict(required=True), + api_key=dict(required=True, no_log=True), + statuspage=dict(required=True), + state=dict(required=False, default='present', + choices=['present', 'absent']), + url=dict(default='https://api.status.io', required=False), + components=dict(type='list', required=False, default=None, + aliases=['component']), + containers=dict(type='list', required=False, default=None, + aliases=['container']), + all_infrastructure_affected=dict(type='bool', default=False, + required=False), + automation=dict(type='bool', default=False, required=False), + title=dict(required=False, default='A new maintenance window'), + desc=dict(required=False, default='Created by Ansible'), + minutes=dict(type='int', required=False, default=10), + maintenance_notify_now=dict(type='bool', default=False, + required=False), + maintenance_notify_72_hr=dict(type='bool', default=False, + required=False), + maintenance_notify_24_hr=dict(type='bool', default=False, + required=False), + maintenance_notify_1_hr=dict(type='bool', default=False, + required=False), + maintenance_id=dict(required=False, default=None), + start_date=dict(default=None, required=False), + start_time=dict(default=None, required=False) + ), + supports_check_mode=True, + ) + + api_id = module.params['api_id'] + api_key = module.params['api_key'] + statuspage = module.params['statuspage'] + state = module.params['state'] + url = module.params['url'] + components = module.params['components'] + containers = module.params['containers'] + all_infrastructure_affected = module.params['all_infrastructure_affected'] + automation = module.params['automation'] + title = module.params['title'] + desc = module.params['desc'] + minutes = module.params['minutes'] + maintenance_notify_now = module.params['maintenance_notify_now'] + maintenance_notify_72_hr = module.params['maintenance_notify_72_hr'] + maintenance_notify_24_hr = module.params['maintenance_notify_24_hr'] + maintenance_notify_1_hr = module.params['maintenance_notify_1_hr'] + maintenance_id = module.params['maintenance_id'] + start_date = module.params['start_date'] + start_time = module.params['start_time'] + + if state == "present": + + if api_id and api_key: + (rc, auth_headers, auth_content, error) = \ + get_api_auth_headers(api_id, api_key, url, statuspage) + if rc != 0: + module.fail_json(msg="Failed to get auth keys: %s" % error) + else: + auth_headers = {} + auth_content = {} + + if minutes or start_time and start_date: + (rc, returned_date, error) = get_date_time( + start_date, start_time, minutes) + if rc != 0: + module.fail_json(msg="Failed to set date/time: %s" % error) + + if not components and not containers: + return module.fail_json(msg="A Component or Container must be " + "defined") + elif components and containers: + return module.fail_json(msg="Components and containers cannot " + "be used together") + else: + if components: + (rc, host_ids, error) = get_component_ids(auth_content, + components) + if rc != 0: + module.fail_json(msg="Failed to find component %s" % error) + + if containers: + (rc, host_ids, error) = get_container_ids(auth_content, + containers) + if rc != 0: + module.fail_json(msg="Failed to find container %s" % error) + + if module.check_mode: + module.exit_json(changed=True) + else: + (rc, _, error) = create_maintenance( + auth_headers, url, statuspage, host_ids, + all_infrastructure_affected, automation, + title, desc, returned_date, maintenance_notify_now, + maintenance_notify_72_hr, maintenance_notify_24_hr, + maintenance_notify_1_hr) + if rc == 0: + module.exit_json(changed=True, result="Successfully created " + "maintenance") + else: + module.fail_json(msg="Failed to create maintenance: %s" + % error) + + if state == "absent": + + if api_id and api_key: + (rc, auth_headers, auth_content, error) = \ + get_api_auth_headers(api_id, api_key, url, statuspage) + if rc != 0: + module.fail_json(msg="Failed to get auth keys: %s" % error) + else: + auth_headers = {} + + if module.check_mode: + module.exit_json(changed=True) + else: + (rc, _, error) = delete_maintenance( + auth_headers, url, statuspage, maintenance_id) + if rc == 0: + module.exit_json( + changed=True, + result="Successfully deleted maintenance" + ) + else: + module.fail_json( + msg="Failed to delete maintenance: %s" % error) + +from ansible.module_utils.basic import * +from ansible.module_utils.urls import * +if __name__ == '__main__': + main()