mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add downtime module (#1437)
* Add downtime module * review + tests * importorskip + review * py36 * make py3.6+ dep explicit
This commit is contained in:
parent
496be77a2b
commit
19a5975181
4 changed files with 536 additions and 0 deletions
1
plugins/modules/datadog_downtime.py
Symbolic link
1
plugins/modules/datadog_downtime.py
Symbolic link
|
@ -0,0 +1 @@
|
|||
./monitoring/datadog/datadog_downtime.py
|
308
plugins/modules/monitoring/datadog/datadog_downtime.py
Normal file
308
plugins/modules/monitoring/datadog/datadog_downtime.py
Normal file
|
@ -0,0 +1,308 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2020, Datadog, Inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
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/s).
|
||||
author:
|
||||
- Datadog (@Datadog)
|
||||
requirements:
|
||||
- datadog-api-client
|
||||
- Python 3.6+
|
||||
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
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
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"]
|
||||
)
|
||||
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()
|
224
tests/unit/plugins/modules/monitoring/test_datadog_downtime.py
Normal file
224
tests/unit/plugins/modules/monitoring/test_datadog_downtime.py
Normal file
|
@ -0,0 +1,224 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible_collections.community.general.plugins.modules.monitoring.datadog import datadog_downtime
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import MagicMock, patch
|
||||
from ansible_collections.community.general.tests.unit.plugins.modules.utils import (
|
||||
AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
||||
)
|
||||
|
||||
from pytest import importorskip
|
||||
|
||||
# Skip this test if python 2 so datadog_api_client cannot be installed
|
||||
datadog_api_client = importorskip("datadog_api_client")
|
||||
Downtime = datadog_api_client.v1.model.downtime.Downtime
|
||||
DowntimeRecurrence = datadog_api_client.v1.model.downtime_recurrence.DowntimeRecurrence
|
||||
|
||||
|
||||
class TestDatadogDowntime(ModuleTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDatadogDowntime, self).setUp()
|
||||
self.module = datadog_downtime
|
||||
|
||||
def tearDown(self):
|
||||
super(TestDatadogDowntime, self).tearDown()
|
||||
|
||||
def test_without_required_parameters(self):
|
||||
"""Failure must occurs when all parameters are missing"""
|
||||
with self.assertRaises(AnsibleFailJson):
|
||||
set_module_args({})
|
||||
self.module.main()
|
||||
|
||||
@patch("ansible_collections.community.general.plugins.modules.monitoring.datadog.datadog_downtime.DowntimesApi")
|
||||
def test_create_downtime_when_no_id(self, downtimes_api_mock):
|
||||
set_module_args({
|
||||
"monitor_tags": ["foo:bar"],
|
||||
"scope": ["*"],
|
||||
"monitor_id": 12345,
|
||||
"downtime_message": "Message",
|
||||
"start": 1111,
|
||||
"end": 2222,
|
||||
"timezone": "UTC",
|
||||
"rrule": "rrule",
|
||||
"api_key": "an_api_key",
|
||||
"app_key": "an_app_key",
|
||||
})
|
||||
|
||||
downtime = Downtime()
|
||||
downtime.monitor_tags = ["foo:bar"]
|
||||
downtime.scope = ["*"]
|
||||
downtime.monitor_id = 12345
|
||||
downtime.message = "Message"
|
||||
downtime.start = 1111
|
||||
downtime.end = 2222
|
||||
downtime.timezone = "UTC"
|
||||
downtime.recurrence = DowntimeRecurrence(
|
||||
rrule="rrule"
|
||||
)
|
||||
|
||||
create_downtime_mock = MagicMock(return_value=Downtime(id=12345))
|
||||
downtimes_api_mock.return_value = MagicMock(create_downtime=create_downtime_mock)
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
self.module.main()
|
||||
self.assertTrue(result.exception.args[0]['changed'])
|
||||
self.assertEqual(result.exception.args[0]['downtime']['id'], 12345)
|
||||
create_downtime_mock.assert_called_once_with(downtime)
|
||||
|
||||
@patch("ansible_collections.community.general.plugins.modules.monitoring.datadog.datadog_downtime.DowntimesApi")
|
||||
def test_create_downtime_when_id_and_disabled(self, downtimes_api_mock):
|
||||
set_module_args({
|
||||
"id": 1212,
|
||||
"monitor_tags": ["foo:bar"],
|
||||
"scope": ["*"],
|
||||
"monitor_id": 12345,
|
||||
"downtime_message": "Message",
|
||||
"start": 1111,
|
||||
"end": 2222,
|
||||
"timezone": "UTC",
|
||||
"rrule": "rrule",
|
||||
"api_key": "an_api_key",
|
||||
"app_key": "an_app_key",
|
||||
})
|
||||
|
||||
downtime = Downtime()
|
||||
downtime.monitor_tags = ["foo:bar"]
|
||||
downtime.scope = ["*"]
|
||||
downtime.monitor_id = 12345
|
||||
downtime.message = "Message"
|
||||
downtime.start = 1111
|
||||
downtime.end = 2222
|
||||
downtime.timezone = "UTC"
|
||||
downtime.recurrence = DowntimeRecurrence(
|
||||
rrule="rrule"
|
||||
)
|
||||
|
||||
create_downtime_mock = MagicMock(return_value=Downtime(id=12345))
|
||||
get_downtime_mock = MagicMock(return_value=Downtime(id=1212, disabled=True))
|
||||
downtimes_api_mock.return_value = MagicMock(
|
||||
create_downtime=create_downtime_mock, get_downtime=get_downtime_mock
|
||||
)
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
self.module.main()
|
||||
self.assertTrue(result.exception.args[0]['changed'])
|
||||
self.assertEqual(result.exception.args[0]['downtime']['id'], 12345)
|
||||
create_downtime_mock.assert_called_once_with(downtime)
|
||||
get_downtime_mock.assert_called_once_with(1212)
|
||||
|
||||
@patch("ansible_collections.community.general.plugins.modules.monitoring.datadog.datadog_downtime.DowntimesApi")
|
||||
def test_update_downtime_when_not_disabled(self, downtimes_api_mock):
|
||||
set_module_args({
|
||||
"id": 1212,
|
||||
"monitor_tags": ["foo:bar"],
|
||||
"scope": ["*"],
|
||||
"monitor_id": 12345,
|
||||
"downtime_message": "Message",
|
||||
"start": 1111,
|
||||
"end": 2222,
|
||||
"timezone": "UTC",
|
||||
"rrule": "rrule",
|
||||
"api_key": "an_api_key",
|
||||
"app_key": "an_app_key",
|
||||
})
|
||||
|
||||
downtime = Downtime()
|
||||
downtime.monitor_tags = ["foo:bar"]
|
||||
downtime.scope = ["*"]
|
||||
downtime.monitor_id = 12345
|
||||
downtime.message = "Message"
|
||||
downtime.start = 1111
|
||||
downtime.end = 2222
|
||||
downtime.timezone = "UTC"
|
||||
downtime.recurrence = DowntimeRecurrence(
|
||||
rrule="rrule"
|
||||
)
|
||||
|
||||
update_downtime_mock = MagicMock(return_value=Downtime(id=1212))
|
||||
get_downtime_mock = MagicMock(return_value=Downtime(id=1212, disabled=False))
|
||||
downtimes_api_mock.return_value = MagicMock(
|
||||
update_downtime=update_downtime_mock, get_downtime=get_downtime_mock
|
||||
)
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
self.module.main()
|
||||
self.assertTrue(result.exception.args[0]['changed'])
|
||||
self.assertEqual(result.exception.args[0]['downtime']['id'], 1212)
|
||||
update_downtime_mock.assert_called_once_with(1212, downtime)
|
||||
get_downtime_mock.assert_called_once_with(1212)
|
||||
|
||||
@patch("ansible_collections.community.general.plugins.modules.monitoring.datadog.datadog_downtime.DowntimesApi")
|
||||
def test_update_downtime_no_change(self, downtimes_api_mock):
|
||||
set_module_args({
|
||||
"id": 1212,
|
||||
"monitor_tags": ["foo:bar"],
|
||||
"scope": ["*"],
|
||||
"monitor_id": 12345,
|
||||
"downtime_message": "Message",
|
||||
"start": 1111,
|
||||
"end": 2222,
|
||||
"timezone": "UTC",
|
||||
"rrule": "rrule",
|
||||
"api_key": "an_api_key",
|
||||
"app_key": "an_app_key",
|
||||
})
|
||||
|
||||
downtime = Downtime()
|
||||
downtime.monitor_tags = ["foo:bar"]
|
||||
downtime.scope = ["*"]
|
||||
downtime.monitor_id = 12345
|
||||
downtime.message = "Message"
|
||||
downtime.start = 1111
|
||||
downtime.end = 2222
|
||||
downtime.timezone = "UTC"
|
||||
downtime.recurrence = DowntimeRecurrence(
|
||||
rrule="rrule"
|
||||
)
|
||||
|
||||
downtime_get = Downtime()
|
||||
downtime_get.id = 1212
|
||||
downtime_get.disabled = False
|
||||
downtime_get.monitor_tags = ["foo:bar"]
|
||||
downtime_get.scope = ["*"]
|
||||
downtime_get.monitor_id = 12345
|
||||
downtime_get.message = "Message"
|
||||
downtime_get.start = 1111
|
||||
downtime_get.end = 2222
|
||||
downtime_get.timezone = "UTC"
|
||||
downtime_get.recurrence = DowntimeRecurrence(
|
||||
rrule="rrule"
|
||||
)
|
||||
|
||||
update_downtime_mock = MagicMock(return_value=downtime_get)
|
||||
get_downtime_mock = MagicMock(return_value=downtime_get)
|
||||
downtimes_api_mock.return_value = MagicMock(
|
||||
update_downtime=update_downtime_mock, get_downtime=get_downtime_mock
|
||||
)
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
self.module.main()
|
||||
self.assertFalse(result.exception.args[0]['changed'])
|
||||
self.assertEqual(result.exception.args[0]['downtime']['id'], 1212)
|
||||
update_downtime_mock.assert_called_once_with(1212, downtime)
|
||||
get_downtime_mock.assert_called_once_with(1212)
|
||||
|
||||
@patch("ansible_collections.community.general.plugins.modules.monitoring.datadog.datadog_downtime.DowntimesApi")
|
||||
def test_delete_downtime(self, downtimes_api_mock):
|
||||
set_module_args({
|
||||
"id": 1212,
|
||||
"state": "absent",
|
||||
"api_key": "an_api_key",
|
||||
"app_key": "an_app_key",
|
||||
})
|
||||
|
||||
cancel_downtime_mock = MagicMock()
|
||||
get_downtime_mock = MagicMock(return_value=Downtime(id=1212))
|
||||
downtimes_api_mock.return_value = MagicMock(
|
||||
get_downtime=get_downtime_mock,
|
||||
cancel_downtime=cancel_downtime_mock
|
||||
)
|
||||
with self.assertRaises(AnsibleExitJson) as result:
|
||||
self.module.main()
|
||||
self.assertTrue(result.exception.args[0]['changed'])
|
||||
cancel_downtime_mock.assert_called_once_with(1212)
|
|
@ -21,3 +21,6 @@ openshift ; python_version >= '2.7'
|
|||
# requirement for maven_artifact module
|
||||
lxml
|
||||
semantic_version
|
||||
|
||||
# requirement for datadog_downtime module
|
||||
datadog-api-client >= 1.0.0b3 ; python_version >= '3.6'
|
Loading…
Reference in a new issue