2020-03-09 10:11:07 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
2022-08-07 13:37:23 +02:00
|
|
|
# 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
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
2021-01-12 07:12:03 +01:00
|
|
|
name: splunk
|
2023-01-04 22:06:57 +01:00
|
|
|
type: notification
|
2020-03-09 10:11:07 +01:00
|
|
|
short_description: Sends task result events to Splunk HTTP Event Collector
|
2020-09-28 21:21:51 +02:00
|
|
|
author: "Stuart Hirst (!UNKNOWN) <support@convergingdata.com>"
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- This callback plugin will send task results as JSON formatted events to a Splunk HTTP collector.
|
2023-01-07 11:03:58 +01:00
|
|
|
- The companion Splunk Monitoring & Diagnostics App is available here U(https://splunkbase.splunk.com/app/4023/).
|
2020-03-09 10:11:07 +01:00
|
|
|
- Credit to "Ryan Currah (@ryancurrah)" for original source upon which this is based.
|
|
|
|
requirements:
|
|
|
|
- Whitelisting this callback plugin
|
|
|
|
- 'Create a HTTP Event Collector in Splunk'
|
2023-01-07 11:03:58 +01:00
|
|
|
- 'Define the URL and token in C(ansible.cfg)'
|
2020-03-09 10:11:07 +01:00
|
|
|
options:
|
|
|
|
url:
|
2023-01-07 11:03:58 +01:00
|
|
|
description: URL to the Splunk HTTP collector source.
|
2024-07-21 21:05:34 +02:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
env:
|
|
|
|
- name: SPLUNK_URL
|
|
|
|
ini:
|
|
|
|
- section: callback_splunk
|
|
|
|
key: url
|
|
|
|
authtoken:
|
2023-01-07 11:03:58 +01:00
|
|
|
description: Token to authenticate the connection to the Splunk HTTP collector.
|
2024-07-21 21:05:34 +02:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
env:
|
|
|
|
- name: SPLUNK_AUTHTOKEN
|
|
|
|
ini:
|
|
|
|
- section: callback_splunk
|
|
|
|
key: authtoken
|
2020-06-29 16:14:44 +02:00
|
|
|
validate_certs:
|
|
|
|
description: Whether to validate certificates for connections to HEC. It is not recommended to set to
|
2023-06-10 09:28:40 +02:00
|
|
|
V(false) except when you are sure that nobody can intercept the connection
|
|
|
|
between this plugin and HEC, as setting it to V(false) allows man-in-the-middle attacks!
|
2020-06-29 16:14:44 +02:00
|
|
|
env:
|
|
|
|
- name: SPLUNK_VALIDATE_CERTS
|
|
|
|
ini:
|
|
|
|
- section: callback_splunk
|
|
|
|
key: validate_certs
|
|
|
|
type: bool
|
|
|
|
default: true
|
|
|
|
version_added: '1.0.0'
|
2020-12-12 09:00:15 +01:00
|
|
|
include_milliseconds:
|
|
|
|
description: Whether to include milliseconds as part of the generated timestamp field in the event
|
2023-01-07 11:03:58 +01:00
|
|
|
sent to the Splunk HTTP collector.
|
2020-12-12 09:00:15 +01:00
|
|
|
env:
|
|
|
|
- name: SPLUNK_INCLUDE_MILLISECONDS
|
|
|
|
ini:
|
|
|
|
- section: callback_splunk
|
|
|
|
key: include_milliseconds
|
|
|
|
type: bool
|
|
|
|
default: false
|
|
|
|
version_added: 2.0.0
|
2021-06-16 19:58:09 +02:00
|
|
|
batch:
|
|
|
|
description:
|
|
|
|
- Correlation ID which can be set across multiple playbook executions.
|
|
|
|
env:
|
|
|
|
- name: SPLUNK_BATCH
|
|
|
|
ini:
|
|
|
|
- section: callback_splunk
|
|
|
|
key: batch
|
|
|
|
type: str
|
|
|
|
version_added: 3.3.0
|
2020-03-09 10:11:07 +01:00
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
examples: >
|
|
|
|
To enable, add this to your ansible.cfg file in the defaults block
|
|
|
|
[defaults]
|
2020-08-08 22:04:34 +02:00
|
|
|
callback_whitelist = community.general.splunk
|
2020-03-09 10:11:07 +01:00
|
|
|
Set the environment variable
|
|
|
|
export SPLUNK_URL=http://mysplunkinstance.datapaas.io:8088/services/collector/event
|
|
|
|
export SPLUNK_AUTHTOKEN=f23blad6-5965-4537-bf69-5b5a545blabla88
|
|
|
|
Set the ansible.cfg variable in the callback_splunk block
|
|
|
|
[callback_splunk]
|
|
|
|
url = http://mysplunkinstance.datapaas.io:8088/services/collector/event
|
|
|
|
authtoken = f23blad6-5965-4537-bf69-5b5a545blabla88
|
|
|
|
'''
|
|
|
|
|
|
|
|
import json
|
|
|
|
import uuid
|
|
|
|
import socket
|
|
|
|
import getpass
|
|
|
|
|
|
|
|
from os.path import basename
|
|
|
|
|
|
|
|
from ansible.module_utils.urls import open_url
|
|
|
|
from ansible.parsing.ajson import AnsibleJSONEncoder
|
|
|
|
from ansible.plugins.callback import CallbackBase
|
|
|
|
|
2024-04-20 09:26:08 +02:00
|
|
|
from ansible_collections.community.general.plugins.module_utils.datetime import (
|
|
|
|
now,
|
|
|
|
)
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
class SplunkHTTPCollectorSource(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.ansible_check_mode = False
|
|
|
|
self.ansible_playbook = ""
|
|
|
|
self.ansible_version = ""
|
|
|
|
self.session = str(uuid.uuid4())
|
|
|
|
self.host = socket.gethostname()
|
|
|
|
self.ip_address = socket.gethostbyname(socket.gethostname())
|
|
|
|
self.user = getpass.getuser()
|
|
|
|
|
2021-06-16 19:58:09 +02:00
|
|
|
def send_event(self, url, authtoken, validate_certs, include_milliseconds, batch, state, result, runtime):
|
2020-03-09 10:11:07 +01:00
|
|
|
if result._task_fields['args'].get('_ansible_check_mode') is True:
|
|
|
|
self.ansible_check_mode = True
|
|
|
|
|
|
|
|
if result._task_fields['args'].get('_ansible_version'):
|
|
|
|
self.ansible_version = \
|
|
|
|
result._task_fields['args'].get('_ansible_version')
|
|
|
|
|
|
|
|
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
|
2021-06-16 19:58:09 +02:00
|
|
|
if batch is not None:
|
|
|
|
data['batch'] = batch
|
2020-03-09 10:11:07 +01:00
|
|
|
data['status'] = state
|
2020-12-12 09:00:15 +01:00
|
|
|
|
|
|
|
if include_milliseconds:
|
|
|
|
time_format = '%Y-%m-%d %H:%M:%S.%f +0000'
|
|
|
|
else:
|
|
|
|
time_format = '%Y-%m-%d %H:%M:%S +0000'
|
|
|
|
|
2024-04-20 09:26:08 +02:00
|
|
|
data['timestamp'] = now().strftime(time_format)
|
2020-03-09 10:11:07 +01:00
|
|
|
data['host'] = self.host
|
|
|
|
data['ip_address'] = self.ip_address
|
|
|
|
data['user'] = self.user
|
|
|
|
data['runtime'] = runtime
|
|
|
|
data['ansible_version'] = self.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
|
|
|
|
|
|
|
|
# This wraps the json payload in and outer json event needed by Splunk
|
|
|
|
jsondata = json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True)
|
|
|
|
jsondata = '{"event":' + jsondata + "}"
|
|
|
|
|
|
|
|
open_url(
|
|
|
|
url,
|
|
|
|
jsondata,
|
|
|
|
headers={
|
|
|
|
'Content-type': 'application/json',
|
|
|
|
'Authorization': 'Splunk ' + authtoken
|
|
|
|
},
|
2020-06-29 16:14:44 +02:00
|
|
|
method='POST',
|
|
|
|
validate_certs=validate_certs
|
2020-03-09 10:11:07 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class CallbackModule(CallbackBase):
|
|
|
|
CALLBACK_VERSION = 2.0
|
2023-01-04 22:06:57 +01:00
|
|
|
CALLBACK_TYPE = 'notification'
|
2020-03-09 10:11:07 +01:00
|
|
|
CALLBACK_NAME = 'community.general.splunk'
|
|
|
|
CALLBACK_NEEDS_WHITELIST = True
|
|
|
|
|
|
|
|
def __init__(self, display=None):
|
|
|
|
super(CallbackModule, self).__init__(display=display)
|
|
|
|
self.start_datetimes = {} # Collect task start times
|
|
|
|
self.url = None
|
|
|
|
self.authtoken = None
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs = None
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds = None
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch = None
|
2020-03-09 10:11:07 +01:00
|
|
|
self.splunk = SplunkHTTPCollectorSource()
|
|
|
|
|
|
|
|
def _runtime(self, result):
|
|
|
|
return (
|
2024-04-20 09:26:08 +02:00
|
|
|
now() -
|
2020-03-09 10:11:07 +01:00
|
|
|
self.start_datetimes[result._task._uuid]
|
|
|
|
).total_seconds()
|
|
|
|
|
|
|
|
def set_options(self, task_keys=None, var_options=None, direct=None):
|
2020-06-29 16:14:44 +02:00
|
|
|
super(CallbackModule, self).set_options(task_keys=task_keys,
|
|
|
|
var_options=var_options,
|
|
|
|
direct=direct)
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
self.url = self.get_option('url')
|
|
|
|
|
|
|
|
if self.url is None:
|
|
|
|
self.disabled = True
|
|
|
|
self._display.warning('Splunk HTTP collector source URL was '
|
|
|
|
'not provided. The Splunk HTTP collector '
|
|
|
|
'source URL can be provided using the '
|
|
|
|
'`SPLUNK_URL` environment variable or '
|
|
|
|
'in the ansible.cfg file.')
|
|
|
|
|
|
|
|
self.authtoken = self.get_option('authtoken')
|
|
|
|
|
|
|
|
if self.authtoken is None:
|
|
|
|
self.disabled = True
|
|
|
|
self._display.warning('Splunk HTTP collector requires an authentication'
|
|
|
|
'token. The Splunk HTTP collector '
|
|
|
|
'authentication token can be provided using the '
|
|
|
|
'`SPLUNK_AUTHTOKEN` environment variable or '
|
|
|
|
'in the ansible.cfg file.')
|
|
|
|
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs = self.get_option('validate_certs')
|
|
|
|
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds = self.get_option('include_milliseconds')
|
|
|
|
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch = self.get_option('batch')
|
|
|
|
|
2020-03-09 10:11:07 +01:00
|
|
|
def v2_playbook_on_start(self, playbook):
|
|
|
|
self.splunk.ansible_playbook = basename(playbook._file_name)
|
|
|
|
|
|
|
|
def v2_playbook_on_task_start(self, task, is_conditional):
|
2024-04-20 09:26:08 +02:00
|
|
|
self.start_datetimes[task._uuid] = now()
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
def v2_playbook_on_handler_task_start(self, task):
|
2024-04-20 09:26:08 +02:00
|
|
|
self.start_datetimes[task._uuid] = now()
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
def v2_runner_on_ok(self, result, **kwargs):
|
|
|
|
self.splunk.send_event(
|
|
|
|
self.url,
|
|
|
|
self.authtoken,
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs,
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds,
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch,
|
2020-03-09 10:11:07 +01:00
|
|
|
'OK',
|
|
|
|
result,
|
|
|
|
self._runtime(result)
|
|
|
|
)
|
|
|
|
|
|
|
|
def v2_runner_on_skipped(self, result, **kwargs):
|
|
|
|
self.splunk.send_event(
|
|
|
|
self.url,
|
|
|
|
self.authtoken,
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs,
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds,
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch,
|
2020-03-09 10:11:07 +01:00
|
|
|
'SKIPPED',
|
|
|
|
result,
|
|
|
|
self._runtime(result)
|
|
|
|
)
|
|
|
|
|
|
|
|
def v2_runner_on_failed(self, result, **kwargs):
|
|
|
|
self.splunk.send_event(
|
|
|
|
self.url,
|
|
|
|
self.authtoken,
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs,
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds,
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch,
|
2020-03-09 10:11:07 +01:00
|
|
|
'FAILED',
|
|
|
|
result,
|
|
|
|
self._runtime(result)
|
|
|
|
)
|
|
|
|
|
|
|
|
def runner_on_async_failed(self, result, **kwargs):
|
|
|
|
self.splunk.send_event(
|
|
|
|
self.url,
|
|
|
|
self.authtoken,
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs,
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds,
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch,
|
2020-03-09 10:11:07 +01:00
|
|
|
'FAILED',
|
|
|
|
result,
|
|
|
|
self._runtime(result)
|
|
|
|
)
|
|
|
|
|
|
|
|
def v2_runner_on_unreachable(self, result, **kwargs):
|
|
|
|
self.splunk.send_event(
|
|
|
|
self.url,
|
|
|
|
self.authtoken,
|
2020-06-29 16:14:44 +02:00
|
|
|
self.validate_certs,
|
2020-12-12 09:00:15 +01:00
|
|
|
self.include_milliseconds,
|
2021-06-16 19:58:09 +02:00
|
|
|
self.batch,
|
2020-03-09 10:11:07 +01:00
|
|
|
'UNREACHABLE',
|
|
|
|
result,
|
|
|
|
self._runtime(result)
|
|
|
|
)
|