1# -*- coding: utf-8 -*-
2# This file is part of Ansible
3#
4# Ansible is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# Ansible is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
16
17from __future__ import (absolute_import, division, print_function)
18__metaclass__ = type
19
20DOCUMENTATION = '''
21name: sumologic
22type: aggregate
23short_description: Sends task result events to Sumologic
24author: "Ryan Currah (@ryancurrah)"
25description:
26  - This callback plugin will send task results as JSON formatted events to a Sumologic HTTP collector source
27requirements:
28  - Whitelisting this callback plugin
29  - 'Create a HTTP collector source in Sumologic and specify a custom timestamp format of C(yyyy-MM-dd HH:mm:ss ZZZZ) and a custom timestamp locator
30    of C("timestamp": "(.*)")'
31options:
32  url:
33    description: URL to the Sumologic HTTP collector source
34    env:
35      - name: SUMOLOGIC_URL
36    ini:
37      - section: callback_sumologic
38        key: url
39'''
40
41EXAMPLES = '''
42examples: >
43  To enable, add this to your ansible.cfg file in the defaults block
44    [defaults]
45    callback_whitelist = community.general.sumologic
46
47  Set the environment variable
48    export SUMOLOGIC_URL=https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/R8moSv1d8EW9LAUFZJ6dbxCFxwLH6kfCdcBfddlfxCbLuL-BN5twcTpMk__pYy_cDmp==
49
50  Set the ansible.cfg variable in the callback_sumologic block
51    [callback_sumologic]
52    url = https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/R8moSv1d8EW9LAUFZJ6dbxCFxwLH6kfCdcBfddlfxCbLuL-BN5twcTpMk__pYy_cDmp==
53'''
54
55import json
56import uuid
57import socket
58import getpass
59
60from datetime import datetime
61from os.path import basename
62
63from ansible.module_utils.urls import open_url
64from ansible.parsing.ajson import AnsibleJSONEncoder
65from ansible.plugins.callback import CallbackBase
66
67
68class SumologicHTTPCollectorSource(object):
69    def __init__(self):
70        self.ansible_check_mode = False
71        self.ansible_playbook = ""
72        self.ansible_version = ""
73        self.session = str(uuid.uuid4())
74        self.host = socket.gethostname()
75        self.ip_address = socket.gethostbyname(socket.gethostname())
76        self.user = getpass.getuser()
77
78    def send_event(self, url, state, result, runtime):
79        if result._task_fields['args'].get('_ansible_check_mode') is True:
80            self.ansible_check_mode = True
81
82        if result._task_fields['args'].get('_ansible_version'):
83            self.ansible_version = \
84                result._task_fields['args'].get('_ansible_version')
85
86        if result._task._role:
87            ansible_role = str(result._task._role)
88        else:
89            ansible_role = None
90
91        if 'args' in result._task_fields:
92            del result._task_fields['args']
93
94        data = {}
95        data['uuid'] = result._task._uuid
96        data['session'] = self.session
97        data['status'] = state
98        data['timestamp'] = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S '
99                                                       '+0000')
100        data['host'] = self.host
101        data['ip_address'] = self.ip_address
102        data['user'] = self.user
103        data['runtime'] = runtime
104        data['ansible_version'] = self.ansible_version
105        data['ansible_check_mode'] = self.ansible_check_mode
106        data['ansible_host'] = result._host.name
107        data['ansible_playbook'] = self.ansible_playbook
108        data['ansible_role'] = ansible_role
109        data['ansible_task'] = result._task_fields
110        data['ansible_result'] = result._result
111
112        open_url(
113            url,
114            data=json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True),
115            headers={
116                'Content-type': 'application/json',
117                'X-Sumo-Host': data['ansible_host']
118            },
119            method='POST'
120        )
121
122
123class CallbackModule(CallbackBase):
124    CALLBACK_VERSION = 2.0
125    CALLBACK_TYPE = 'aggregate'
126    CALLBACK_NAME = 'community.general.sumologic'
127    CALLBACK_NEEDS_WHITELIST = True
128
129    def __init__(self, display=None):
130        super(CallbackModule, self).__init__(display=display)
131        self.start_datetimes = {}  # Collect task start times
132        self.url = None
133        self.sumologic = SumologicHTTPCollectorSource()
134
135    def _runtime(self, result):
136        return (
137            datetime.utcnow() -
138            self.start_datetimes[result._task._uuid]
139        ).total_seconds()
140
141    def set_options(self, task_keys=None, var_options=None, direct=None):
142        super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
143
144        self.url = self.get_option('url')
145
146        if self.url is None:
147            self.disabled = True
148            self._display.warning('Sumologic HTTP collector source URL was '
149                                  'not provided. The Sumologic HTTP collector '
150                                  'source URL can be provided using the '
151                                  '`SUMOLOGIC_URL` environment variable or '
152                                  'in the ansible.cfg file.')
153
154    def v2_playbook_on_start(self, playbook):
155        self.sumologic.ansible_playbook = basename(playbook._file_name)
156
157    def v2_playbook_on_task_start(self, task, is_conditional):
158        self.start_datetimes[task._uuid] = datetime.utcnow()
159
160    def v2_playbook_on_handler_task_start(self, task):
161        self.start_datetimes[task._uuid] = datetime.utcnow()
162
163    def v2_runner_on_ok(self, result, **kwargs):
164        self.sumologic.send_event(
165            self.url,
166            'OK',
167            result,
168            self._runtime(result)
169        )
170
171    def v2_runner_on_skipped(self, result, **kwargs):
172        self.sumologic.send_event(
173            self.url,
174            'SKIPPED',
175            result,
176            self._runtime(result)
177        )
178
179    def v2_runner_on_failed(self, result, **kwargs):
180        self.sumologic.send_event(
181            self.url,
182            'FAILED',
183            result,
184            self._runtime(result)
185        )
186
187    def runner_on_async_failed(self, result, **kwargs):
188        self.sumologic.send_event(
189            self.url,
190            'FAILED',
191            result,
192            self._runtime(result)
193        )
194
195    def v2_runner_on_unreachable(self, result, **kwargs):
196        self.sumologic.send_event(
197            self.url,
198            'UNREACHABLE',
199            result,
200            self._runtime(result)
201        )
202