summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/callback/logentries.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/general/plugins/callback/logentries.py
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/plugins/callback/logentries.py')
-rw-r--r--ansible_collections/community/general/plugins/callback/logentries.py332
1 files changed, 332 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/callback/logentries.py b/ansible_collections/community/general/plugins/callback/logentries.py
new file mode 100644
index 000000000..22322a4df
--- /dev/null
+++ b/ansible_collections/community/general/plugins/callback/logentries.py
@@ -0,0 +1,332 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Logentries.com, Jimmy Tang <jimmy.tang@logentries.com>
+# Copyright (c) 2017 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 (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+ author: Unknown (!UNKNOWN)
+ name: logentries
+ type: notification
+ short_description: Sends events to Logentries
+ description:
+ - This callback plugin will generate JSON objects and send them to Logentries via TCP for auditing/debugging purposes.
+ - Before 2.4, if you wanted to use an ini configuration, the file must be placed in the same directory as this plugin and named C(logentries.ini).
+ - In 2.4 and above you can just put it in the main Ansible configuration file.
+ requirements:
+ - whitelisting in configuration
+ - certifi (Python library)
+ - flatdict (Python library), if you want to use the 'flatten' option
+ options:
+ api:
+ description: URI to the Logentries API.
+ env:
+ - name: LOGENTRIES_API
+ default: data.logentries.com
+ ini:
+ - section: callback_logentries
+ key: api
+ port:
+ description: HTTP port to use when connecting to the API.
+ env:
+ - name: LOGENTRIES_PORT
+ default: 80
+ ini:
+ - section: callback_logentries
+ key: port
+ tls_port:
+ description: Port to use when connecting to the API when TLS is enabled.
+ env:
+ - name: LOGENTRIES_TLS_PORT
+ default: 443
+ ini:
+ - section: callback_logentries
+ key: tls_port
+ token:
+ description: The logentries C(TCP token).
+ env:
+ - name: LOGENTRIES_ANSIBLE_TOKEN
+ required: true
+ ini:
+ - section: callback_logentries
+ key: token
+ use_tls:
+ description:
+ - Toggle to decide whether to use TLS to encrypt the communications with the API server.
+ env:
+ - name: LOGENTRIES_USE_TLS
+ default: false
+ type: boolean
+ ini:
+ - section: callback_logentries
+ key: use_tls
+ flatten:
+ description: Flatten complex data structures into a single dictionary with complex keys.
+ type: boolean
+ default: false
+ env:
+ - name: LOGENTRIES_FLATTEN
+ ini:
+ - section: callback_logentries
+ key: flatten
+'''
+
+EXAMPLES = '''
+examples: >
+ To enable, add this to your ansible.cfg file in the defaults block
+
+ [defaults]
+ callback_whitelist = community.general.logentries
+
+ Either set the environment variables
+ export LOGENTRIES_API=data.logentries.com
+ export LOGENTRIES_PORT=10000
+ export LOGENTRIES_ANSIBLE_TOKEN=dd21fc88-f00a-43ff-b977-e3a4233c53af
+
+ Or in the main Ansible config file
+ [callback_logentries]
+ api = data.logentries.com
+ port = 10000
+ tls_port = 20000
+ use_tls = no
+ token = dd21fc88-f00a-43ff-b977-e3a4233c53af
+ flatten = False
+'''
+
+import os
+import socket
+import random
+import time
+import uuid
+
+try:
+ import certifi
+ HAS_CERTIFI = True
+except ImportError:
+ HAS_CERTIFI = False
+
+try:
+ import flatdict
+ HAS_FLATDICT = True
+except ImportError:
+ HAS_FLATDICT = False
+
+from ansible.module_utils.common.text.converters import to_bytes, to_text
+from ansible.plugins.callback import CallbackBase
+
+# Todo:
+# * Better formatting of output before sending out to logentries data/api nodes.
+
+
+class PlainTextSocketAppender(object):
+ def __init__(self, display, LE_API='data.logentries.com', LE_PORT=80, LE_TLS_PORT=443):
+
+ self.LE_API = LE_API
+ self.LE_PORT = LE_PORT
+ self.LE_TLS_PORT = LE_TLS_PORT
+ self.MIN_DELAY = 0.1
+ self.MAX_DELAY = 10
+ # Error message displayed when an incorrect Token has been detected
+ self.INVALID_TOKEN = "\n\nIt appears the LOGENTRIES_TOKEN parameter you entered is incorrect!\n\n"
+ # Unicode Line separator character \u2028
+ self.LINE_SEP = u'\u2028'
+
+ self._display = display
+ self._conn = None
+
+ def open_connection(self):
+ self._conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._conn.connect((self.LE_API, self.LE_PORT))
+
+ def reopen_connection(self):
+ self.close_connection()
+
+ root_delay = self.MIN_DELAY
+ while True:
+ try:
+ self.open_connection()
+ return
+ except Exception as e:
+ self._display.vvvv(u"Unable to connect to Logentries: %s" % to_text(e))
+
+ root_delay *= 2
+ if root_delay > self.MAX_DELAY:
+ root_delay = self.MAX_DELAY
+
+ wait_for = root_delay + random.uniform(0, root_delay)
+
+ try:
+ self._display.vvvv("sleeping %s before retry" % wait_for)
+ time.sleep(wait_for)
+ except KeyboardInterrupt:
+ raise
+
+ def close_connection(self):
+ if self._conn is not None:
+ self._conn.close()
+
+ def put(self, data):
+ # Replace newlines with Unicode line separator
+ # for multi-line events
+ data = to_text(data, errors='surrogate_or_strict')
+ multiline = data.replace(u'\n', self.LINE_SEP)
+ multiline += u"\n"
+ # Send data, reconnect if needed
+ while True:
+ try:
+ self._conn.send(to_bytes(multiline, errors='surrogate_or_strict'))
+ except socket.error:
+ self.reopen_connection()
+ continue
+ break
+
+ self.close_connection()
+
+
+try:
+ import ssl
+ HAS_SSL = True
+except ImportError: # for systems without TLS support.
+ SocketAppender = PlainTextSocketAppender
+ HAS_SSL = False
+else:
+
+ class TLSSocketAppender(PlainTextSocketAppender):
+ def open_connection(self):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock = ssl.wrap_socket(
+ sock=sock,
+ keyfile=None,
+ certfile=None,
+ server_side=False,
+ cert_reqs=ssl.CERT_REQUIRED,
+ ssl_version=getattr(
+ ssl, 'PROTOCOL_TLSv1_2', ssl.PROTOCOL_TLSv1),
+ ca_certs=certifi.where(),
+ do_handshake_on_connect=True,
+ suppress_ragged_eofs=True, )
+ sock.connect((self.LE_API, self.LE_TLS_PORT))
+ self._conn = sock
+
+ SocketAppender = TLSSocketAppender
+
+
+class CallbackModule(CallbackBase):
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = 'notification'
+ CALLBACK_NAME = 'community.general.logentries'
+ CALLBACK_NEEDS_WHITELIST = True
+
+ def __init__(self):
+
+ # TODO: allow for alternate posting methods (REST/UDP/agent/etc)
+ super(CallbackModule, self).__init__()
+
+ # verify dependencies
+ if not HAS_SSL:
+ self._display.warning("Unable to import ssl module. Will send over port 80.")
+
+ if not HAS_CERTIFI:
+ self.disabled = True
+ self._display.warning('The `certifi` python module is not installed.\nDisabling the Logentries callback plugin.')
+
+ self.le_jobid = str(uuid.uuid4())
+
+ # FIXME: make configurable, move to options
+ self.timeout = 10
+
+ def set_options(self, task_keys=None, var_options=None, direct=None):
+
+ super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
+
+ # get options
+ try:
+ self.api_url = self.get_option('api')
+ self.api_port = self.get_option('port')
+ self.api_tls_port = self.get_option('tls_port')
+ self.use_tls = self.get_option('use_tls')
+ self.flatten = self.get_option('flatten')
+ except KeyError as e:
+ self._display.warning(u"Missing option for Logentries callback plugin: %s" % to_text(e))
+ self.disabled = True
+
+ try:
+ self.token = self.get_option('token')
+ except KeyError as e:
+ self._display.warning('Logentries token was not provided, this is required for this callback to operate, disabling')
+ self.disabled = True
+
+ if self.flatten and not HAS_FLATDICT:
+ self.disabled = True
+ self._display.warning('You have chosen to flatten and the `flatdict` python module is not installed.\nDisabling the Logentries callback plugin.')
+
+ self._initialize_connections()
+
+ def _initialize_connections(self):
+
+ if not self.disabled:
+ if self.use_tls:
+ self._display.vvvv("Connecting to %s:%s with TLS" % (self.api_url, self.api_tls_port))
+ self._appender = TLSSocketAppender(display=self._display, LE_API=self.api_url, LE_TLS_PORT=self.api_tls_port)
+ else:
+ self._display.vvvv("Connecting to %s:%s" % (self.api_url, self.api_port))
+ self._appender = PlainTextSocketAppender(display=self._display, LE_API=self.api_url, LE_PORT=self.api_port)
+ self._appender.reopen_connection()
+
+ def emit_formatted(self, record):
+ if self.flatten:
+ results = flatdict.FlatDict(record)
+ self.emit(self._dump_results(results))
+ else:
+ self.emit(self._dump_results(record))
+
+ def emit(self, record):
+ msg = record.rstrip('\n')
+ msg = "{0} {1}".format(self.token, msg)
+ self._appender.put(msg)
+ self._display.vvvv("Sent event to logentries")
+
+ def _set_info(self, host, res):
+ return {'le_jobid': self.le_jobid, 'hostname': host, 'results': res}
+
+ def runner_on_ok(self, host, res):
+ results = self._set_info(host, res)
+ results['status'] = 'OK'
+ self.emit_formatted(results)
+
+ def runner_on_failed(self, host, res, ignore_errors=False):
+ results = self._set_info(host, res)
+ results['status'] = 'FAILED'
+ self.emit_formatted(results)
+
+ def runner_on_skipped(self, host, item=None):
+ results = self._set_info(host, item)
+ del results['results']
+ results['status'] = 'SKIPPED'
+ self.emit_formatted(results)
+
+ def runner_on_unreachable(self, host, res):
+ results = self._set_info(host, res)
+ results['status'] = 'UNREACHABLE'
+ self.emit_formatted(results)
+
+ def runner_on_async_failed(self, host, res, jid):
+ results = self._set_info(host, res)
+ results['jid'] = jid
+ results['status'] = 'ASYNC_FAILED'
+ self.emit_formatted(results)
+
+ def v2_playbook_on_play_start(self, play):
+ results = {}
+ results['le_jobid'] = self.le_jobid
+ results['started_by'] = os.getlogin()
+ if play.name:
+ results['play'] = play.name
+ results['hosts'] = play.hosts
+ self.emit_formatted(results)
+
+ def playbook_on_stats(self, stats):
+ """ close connection """
+ self._appender.close_connection()