#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2020, Datadog, Inc # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ --- module: datadog_downtime short_description: Manages Datadog downtimes version_added: 2.0.0 description: - Manages downtimes within Datadog. - Options as described on U(https://docs.datadoghq.com/api/v1/downtimes/). author: - Datadog (@Datadog) requirements: - datadog-api-client - Python 3.6+ extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: none diff_mode: support: none options: api_key: description: - Your Datadog API key. required: true type: str api_host: description: - The URL to the Datadog API. - This value can also be set with the C(DATADOG_HOST) environment variable. required: false default: https://api.datadoghq.com type: str app_key: description: - Your Datadog app key. required: true type: str state: description: - The designated state of the downtime. required: false choices: ["present", "absent"] default: present type: str id: description: - The identifier of the downtime. - If empty, a new downtime gets created, otherwise it is either updated or deleted depending of the C(state). - To keep your playbook idempotent, you should save the identifier in a file and read it in a lookup. type: int monitor_tags: description: - A list of monitor tags to which the downtime applies. - The resulting downtime applies to monitors that match ALL provided monitor tags. type: list elements: str scope: description: - A list of scopes to which the downtime applies. - The resulting downtime applies to sources that matches ALL provided scopes. type: list elements: str monitor_id: description: - The ID of the monitor to mute. If not provided, the downtime applies to all monitors. type: int downtime_message: description: - A message to include with notifications for this downtime. - Email notifications can be sent to specific users by using the same "@username" notation as events. type: str start: type: int description: - POSIX timestamp to start the downtime. If not provided, the downtime starts the moment it is created. end: type: int description: - POSIX timestamp to end the downtime. If not provided, the downtime is in effect until you cancel it. timezone: description: - The timezone for the downtime. type: str rrule: description: - The C(RRULE) standard for defining recurring events. - For example, to have a recurring event on the first day of each month, select a type of rrule and set the C(FREQ) to C(MONTHLY) and C(BYMONTHDAY) to C(1). - Most common rrule options from the iCalendar Spec are supported. - Attributes specifying the duration in C(RRULE) are not supported (e.g. C(DTSTART), C(DTEND), C(DURATION)). type: str """ EXAMPLES = """ - name: Create a downtime register: downtime_var community.general.datadog_downtime: state: present monitor_tags: - "foo:bar" downtime_message: "Downtime for foo:bar" scope: "test" api_key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" app_key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # Lookup the id in the file and ignore errors if the file doesn't exits, so downtime gets created id: "{{ lookup('file', inventory_hostname ~ '_downtime_id.txt', errors='ignore') }}" - name: Save downtime id to file for later updates and idempotence delegate_to: localhost copy: content: "{{ downtime.downtime.id }}" dest: "{{ inventory_hostname ~ '_downtime_id.txt' }}" """ RETURN = """ # Returns the downtime JSON dictionary from the API response under the C(downtime) key. # See https://docs.datadoghq.com/api/v1/downtimes/#schedule-a-downtime for more details. downtime: description: The downtime returned by the API. type: dict returned: always sample: { "active": true, "canceled": null, "creator_id": 1445416, "disabled": false, "downtime_type": 2, "end": null, "id": 1055751000, "message": "Downtime for foo:bar", "monitor_id": null, "monitor_tags": [ "foo:bar" ], "parent_id": null, "recurrence": null, "scope": [ "test" ], "start": 1607015009, "timezone": "UTC", "updater_id": null } """ import traceback from ansible.module_utils.basic import AnsibleModule, missing_required_lib # Import Datadog DATADOG_IMP_ERR = None HAS_DATADOG = True try: from datadog_api_client.v1 import Configuration, ApiClient, ApiException from datadog_api_client.v1.api.downtimes_api import DowntimesApi from datadog_api_client.v1.model.downtime import Downtime from datadog_api_client.v1.model.downtime_recurrence import DowntimeRecurrence except ImportError: DATADOG_IMP_ERR = traceback.format_exc() HAS_DATADOG = False def main(): module = AnsibleModule( argument_spec=dict( api_key=dict(required=True, no_log=True), api_host=dict(required=False, default="https://api.datadoghq.com"), app_key=dict(required=True, no_log=True), state=dict(required=False, choices=["present", "absent"], default="present"), monitor_tags=dict(required=False, type="list", elements="str"), scope=dict(required=False, type="list", elements="str"), monitor_id=dict(required=False, type="int"), downtime_message=dict(required=False, no_log=True), start=dict(required=False, type="int"), end=dict(required=False, type="int"), timezone=dict(required=False, type="str"), rrule=dict(required=False, type="str"), id=dict(required=False, type="int"), ) ) # Prepare Datadog if not HAS_DATADOG: module.fail_json(msg=missing_required_lib("datadog-api-client"), exception=DATADOG_IMP_ERR) configuration = Configuration( host=module.params["api_host"], api_key={ "apiKeyAuth": module.params["api_key"], "appKeyAuth": module.params["app_key"] } ) with ApiClient(configuration) as api_client: api_client.user_agent = "ansible_collection/community_general (module_name datadog_downtime) {0}".format( api_client.user_agent ) api_instance = DowntimesApi(api_client) # Validate api and app keys try: api_instance.list_downtimes(current_only=True) except ApiException as e: module.fail_json(msg="Failed to connect Datadog server using given app_key and api_key: {0}".format(e)) if module.params["state"] == "present": schedule_downtime(module, api_client) elif module.params["state"] == "absent": cancel_downtime(module, api_client) def _get_downtime(module, api_client): api = DowntimesApi(api_client) downtime = None if module.params["id"]: try: downtime = api.get_downtime(module.params["id"]) except ApiException as e: module.fail_json(msg="Failed to retrieve downtime with id {0}: {1}".format(module.params["id"], e)) return downtime def build_downtime(module): downtime = Downtime() if module.params["monitor_tags"]: downtime.monitor_tags = module.params["monitor_tags"] if module.params["scope"]: downtime.scope = module.params["scope"] if module.params["monitor_id"]: downtime.monitor_id = module.params["monitor_id"] if module.params["downtime_message"]: downtime.message = module.params["downtime_message"] if module.params["start"]: downtime.start = module.params["start"] if module.params["end"]: downtime.end = module.params["end"] if module.params["timezone"]: downtime.timezone = module.params["timezone"] if module.params["rrule"]: downtime.recurrence = DowntimeRecurrence( rrule=module.params["rrule"], type="rrule", ) return downtime def _post_downtime(module, api_client): api = DowntimesApi(api_client) downtime = build_downtime(module) try: resp = api.create_downtime(downtime) module.params["id"] = resp.id module.exit_json(changed=True, downtime=resp.to_dict()) except ApiException as e: module.fail_json(msg="Failed to create downtime: {0}".format(e)) def _equal_dicts(a, b, ignore_keys): ka = set(a).difference(ignore_keys) kb = set(b).difference(ignore_keys) return ka == kb and all(a[k] == b[k] for k in ka) def _update_downtime(module, current_downtime, api_client): api = DowntimesApi(api_client) downtime = build_downtime(module) try: if current_downtime.disabled: resp = api.create_downtime(downtime) else: resp = api.update_downtime(module.params["id"], downtime) if _equal_dicts( resp.to_dict(), current_downtime.to_dict(), ["active", "creator_id", "updater_id"] ): module.exit_json(changed=False, downtime=resp.to_dict()) else: module.exit_json(changed=True, downtime=resp.to_dict()) except ApiException as e: module.fail_json(msg="Failed to update downtime: {0}".format(e)) def schedule_downtime(module, api_client): downtime = _get_downtime(module, api_client) if downtime is None: _post_downtime(module, api_client) else: _update_downtime(module, downtime, api_client) def cancel_downtime(module, api_client): downtime = _get_downtime(module, api_client) api = DowntimesApi(api_client) if downtime is None: module.exit_json(changed=False) try: api.cancel_downtime(downtime["id"]) except ApiException as e: module.fail_json(msg="Failed to create downtime: {0}".format(e)) module.exit_json(changed=True) if __name__ == "__main__": main()