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