# Copyright (c) Ansible Project # 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 annotations DOCUMENTATION = r""" name: sumologic type: notification short_description: Sends task result events to Sumologic author: "Ryan Currah (@ryancurrah)" description: - This callback plugin sends task results as JSON formatted events to a Sumologic HTTP collector source. requirements: - Whitelisting this callback plugin - 'Create a HTTP collector source in Sumologic and specify a custom timestamp format of V(yyyy-MM-dd HH:mm:ss ZZZZ) and a custom timestamp locator of V("timestamp": "(.*\)")' options: url: description: URL to the Sumologic HTTP collector source. type: str env: - name: SUMOLOGIC_URL ini: - section: callback_sumologic key: url """ EXAMPLES = r""" examples: |- To enable, add this to your ansible.cfg file in the defaults block [defaults] callback_whitelist = community.general.sumologic Set the environment variable export SUMOLOGIC_URL=https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/R8moSv1d8EW9LAUFZJ6dbxCFxwLH6kfCdcBfddlfxCbLuL-BN5twcTpMk__pYy_cDmp== Set the ansible.cfg variable in the callback_sumologic block [callback_sumologic] url = https://endpoint1.collection.us2.sumologic.com/receiver/v1/http/R8moSv1d8EW9LAUFZJ6dbxCFxwLH6kfCdcBfddlfxCbLuL-BN5twcTpMk__pYy_cDmp== """ import getpass import json import socket import uuid from os.path import basename from ansible.module_utils.ansible_release import __version__ as ansible_version from ansible.module_utils.urls import open_url from ansible.parsing.ajson import AnsibleJSONEncoder from ansible.plugins.callback import CallbackBase from ansible_collections.community.general.plugins.module_utils.datetime import ( now, ) class SumologicHTTPCollectorSource: def __init__(self): self.ansible_check_mode = False self.ansible_playbook = "" self.session = str(uuid.uuid4()) self.host = socket.gethostname() self.ip_address = socket.gethostbyname(socket.gethostname()) self.user = getpass.getuser() def send_event(self, url, state, result, runtime): if result._task_fields["args"].get("_ansible_check_mode") is True: self.ansible_check_mode = True if result._task._role: ansible_role = str(result._task._role) else: ansible_role = None if "args" in result._task_fields: del result._task_fields["args"] data = {} data["uuid"] = result._task._uuid data["session"] = self.session data["status"] = state data["timestamp"] = now().strftime("%Y-%m-%d %H:%M:%S +0000") data["host"] = self.host data["ip_address"] = self.ip_address data["user"] = self.user data["runtime"] = runtime data["ansible_version"] = ansible_version data["ansible_check_mode"] = self.ansible_check_mode data["ansible_host"] = result._host.name data["ansible_playbook"] = self.ansible_playbook data["ansible_role"] = ansible_role data["ansible_task"] = result._task_fields data["ansible_result"] = result._result open_url( url, data=json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True), headers={"Content-type": "application/json", "X-Sumo-Host": data["ansible_host"]}, method="POST", ) class CallbackModule(CallbackBase): CALLBACK_VERSION = 2.0 CALLBACK_TYPE = "notification" CALLBACK_NAME = "community.general.sumologic" CALLBACK_NEEDS_WHITELIST = True def __init__(self, display=None): super().__init__(display=display) self.start_datetimes = {} # Collect task start times self.url = None self.sumologic = SumologicHTTPCollectorSource() def _runtime(self, result): return (now() - self.start_datetimes[result._task._uuid]).total_seconds() def set_options(self, task_keys=None, var_options=None, direct=None): super().set_options(task_keys=task_keys, var_options=var_options, direct=direct) self.url = self.get_option("url") if self.url is None: self.disabled = True self._display.warning( "Sumologic HTTP collector source URL was " "not provided. The Sumologic HTTP collector " "source URL can be provided using the " "`SUMOLOGIC_URL` environment variable or " "in the ansible.cfg file." ) def v2_playbook_on_start(self, playbook): self.sumologic.ansible_playbook = basename(playbook._file_name) def v2_playbook_on_task_start(self, task, is_conditional): self.start_datetimes[task._uuid] = now() def v2_playbook_on_handler_task_start(self, task): self.start_datetimes[task._uuid] = now() def v2_runner_on_ok(self, result, **kwargs): self.sumologic.send_event(self.url, "OK", result, self._runtime(result)) def v2_runner_on_skipped(self, result, **kwargs): self.sumologic.send_event(self.url, "SKIPPED", result, self._runtime(result)) def v2_runner_on_failed(self, result, **kwargs): self.sumologic.send_event(self.url, "FAILED", result, self._runtime(result)) def runner_on_async_failed(self, result, **kwargs): self.sumologic.send_event(self.url, "FAILED", result, self._runtime(result)) def v2_runner_on_unreachable(self, result, **kwargs): self.sumologic.send_event(self.url, "UNREACHABLE", result, self._runtime(result))