diff --git a/plugins/modules/monitoring/pagerduty_change.py b/plugins/modules/monitoring/pagerduty_change.py new file mode 100644 index 0000000000..3fecdba59f --- /dev/null +++ b/plugins/modules/monitoring/pagerduty_change.py @@ -0,0 +1,192 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2020, Adam Vaughan (@adamvaughan) avaughan@pagerduty.com +# 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: pagerduty_change +short_description: Track a code or infrastructure change as a PagerDuty change event +version_added: 1.3.0 +description: + - This module will let you create a PagerDuty change event each time the module is run. + - This is not an idempotent action and a new change event will be created each time it is run. +author: + - Adam Vaughan (@adamvaughan) +requirements: + - PagerDuty integration key +options: + integration_key: + description: + - The integration key that identifies the service the change was made to. + This can be found by adding an integration to a service in PagerDuty. + required: true + type: str + summary: + description: + - A short description of the change that occurred. + required: true + type: str + source: + description: + - The source of the change event. + default: Ansible + type: str + user: + description: + - The name of the user or process that triggered this deployment. + type: str + repo: + description: + - The URL of the project repository. + required: false + type: str + revision: + description: + - An identifier of the revision being deployed, typically a number or SHA from a version control system. + required: false + type: str + environment: + description: + - The environment name, typically C(production), C(staging), etc. + required: false + type: str + link_url: + description: + - A URL where more information about the deployment can be obtained. + required: false + type: str + link_text: + description: + - Descriptive text for a URL where more information about the deployment can be obtained. + required: false + type: str + url: + description: + - URL to submit the change event to. + required: false + default: https://events.pagerduty.com/v2/change/enqueue + type: str + validate_certs: + description: + - If C(no), SSL certificates for the target URL will not be validated. + This should only be used on personally controlled sites using self-signed certificates. + required: false + default: yes + type: bool +notes: + - Supports C(check_mode). Note that check mode simply does nothing except returning C(changed=true) in case the I(url) seems to be correct. +''' + +EXAMPLES = ''' +- name: Track the deployment as a PagerDuty change event + community.general.pagerduty_change: + integration_key: abc123abc123abc123abc123abc123ab + summary: The application was deployed + +- name: Track the deployment as a PagerDuty change event with more details + community.general.pagerduty_change: + integration_key: abc123abc123abc123abc123abc123ab + summary: The application was deployed + source: Ansible Deploy + user: ansible + repo: github.com/ansible/ansible + revision: '4.2' + environment: production + link_url: https://github.com/ansible-collections/community.general/pull/1269 + link_text: View changes on GitHub +''' + +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.basic import AnsibleModule +from datetime import datetime + + +def main(): + module = AnsibleModule( + argument_spec=dict( + integration_key=dict(required=True, type='str'), + summary=dict(required=True, type='str'), + source=dict(required=False, default='Ansible', type='str'), + user=dict(required=False, type='str'), + repo=dict(required=False, type='str'), + revision=dict(required=False, type='str'), + environment=dict(required=False, type='str'), + link_url=dict(required=False, type='str'), + link_text=dict(required=False, type='str'), + url=dict(required=False, + default='https://events.pagerduty.com/v2/change/enqueue', type='str'), + validate_certs=dict(default=True, type='bool') + ), + supports_check_mode=True + ) + + # API documented at https://developer.pagerduty.com/docs/events-api-v2/send-change-events/ + + url = module.params['url'] + headers = {'Content-Type': 'application/json'} + + if module.check_mode: + _response, info = fetch_url( + module, url, headers=headers, method='POST') + + if info['status'] == 400: + module.exit_json(changed=True) + else: + module.fail_json( + msg='Checking the PagerDuty change event API returned an unexpected response: %d' % (info['status'])) + + custom_details = {} + + if module.params['user']: + custom_details['user'] = module.params['user'] + + if module.params['repo']: + custom_details['repo'] = module.params['repo'] + + if module.params['revision']: + custom_details['revision'] = module.params['revision'] + + if module.params['environment']: + custom_details['environment'] = module.params['environment'] + + now = datetime.utcnow() + timestamp = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + payload = { + 'summary': module.params['summary'], + 'source': module.params['source'], + 'timestamp': timestamp, + 'custom_details': custom_details + } + + event = { + 'routing_key': module.params['integration_key'], + 'payload': payload + } + + if module.params['link_url']: + link = { + 'href': module.params['link_url'] + } + + if module.params['link_text']: + link['text'] = module.params['link_text'] + + event['links'] = [link] + + _response, info = fetch_url( + module, url, data=module.jsonify(event), headers=headers, method='POST') + + if info['status'] == 202: + module.exit_json(changed=True) + else: + module.fail_json( + msg='Creating PagerDuty change event failed with %d' % (info['status'])) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/pagerduty_change.py b/plugins/modules/pagerduty_change.py new file mode 120000 index 0000000000..3c1ee64719 --- /dev/null +++ b/plugins/modules/pagerduty_change.py @@ -0,0 +1 @@ +monitoring/pagerduty_change.py \ No newline at end of file diff --git a/tests/unit/plugins/modules/monitoring/test_pagerduty_change.py b/tests/unit/plugins/modules/monitoring/test_pagerduty_change.py new file mode 100644 index 0000000000..57b62a51ed --- /dev/null +++ b/tests/unit/plugins/modules/monitoring/test_pagerduty_change.py @@ -0,0 +1,82 @@ +# 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) +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest +from ansible_collections.community.general.tests.unit.compat.mock import patch +from ansible_collections.community.general.plugins.modules.monitoring import pagerduty_change +from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args + + +class TestPagerDutyChangeModule(ModuleTestCase): + def setUp(self): + super(TestPagerDutyChangeModule, self).setUp() + self.module = pagerduty_change + + def tearDown(self): + super(TestPagerDutyChangeModule, self).tearDown() + + @pytest.fixture + def fetch_url_mock(self, mocker): + return mocker.patch('ansible.module_utils.monitoring.pagerduty_change.fetch_url') + + def test_module_fail_when_required_args_missing(self): + with self.assertRaises(AnsibleFailJson): + set_module_args({}) + self.module.main() + + def test_ensure_change_event_created_with_minimal_data(self): + set_module_args({ + 'integration_key': 'test', + 'summary': 'Testing' + }) + + with patch.object(pagerduty_change, 'fetch_url') as fetch_url_mock: + fetch_url_mock.return_value = (None, {"status": 202}) + with self.assertRaises(AnsibleExitJson): + self.module.main() + + assert fetch_url_mock.call_count == 1 + url = fetch_url_mock.call_args[0][1] + json_data = fetch_url_mock.call_args[1]['data'] + data = json.loads(json_data) + + assert url == 'https://events.pagerduty.com/v2/change/enqueue' + assert data['routing_key'] == 'test' + assert data['payload']['summary'] == 'Testing' + assert data['payload']['source'] == 'Ansible' + + def test_ensure_change_event_created_with_full_data(self): + set_module_args({ + 'integration_key': 'test', + 'summary': 'Testing', + 'source': 'My Ansible Script', + 'user': 'ansible', + 'repo': 'github.com/ansible/ansible', + 'revision': '8c67432', + 'environment': 'production', + 'link_url': 'https://pagerduty.com', + 'link_text': 'PagerDuty' + }) + + with patch.object(pagerduty_change, 'fetch_url') as fetch_url_mock: + fetch_url_mock.return_value = (None, {"status": 202}) + with self.assertRaises(AnsibleExitJson): + self.module.main() + + assert fetch_url_mock.call_count == 1 + url = fetch_url_mock.call_args[0][1] + json_data = fetch_url_mock.call_args[1]['data'] + data = json.loads(json_data) + + assert url == 'https://events.pagerduty.com/v2/change/enqueue' + assert data['routing_key'] == 'test' + assert data['payload']['summary'] == 'Testing' + assert data['payload']['source'] == 'My Ansible Script' + assert data['payload']['custom_details']['user'] == 'ansible' + assert data['payload']['custom_details']['repo'] == 'github.com/ansible/ansible' + assert data['payload']['custom_details']['revision'] == '8c67432' + assert data['payload']['custom_details']['environment'] == 'production' + assert data['links'][0]['href'] == 'https://pagerduty.com' + assert data['links'][0]['text'] == 'PagerDuty'