summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/callback
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:18:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-05 16:18:41 +0000
commitb643c52cf29ce5bbab738b43290af3556efa1ca9 (patch)
tree21d5c53d7a9b696627a255777cefdf6f78968824 /ansible_collections/community/general/plugins/callback
parentReleasing progress-linux version 9.5.1+dfsg-1~progress7.99u1. (diff)
downloadansible-b643c52cf29ce5bbab738b43290af3556efa1ca9.tar.xz
ansible-b643c52cf29ce5bbab738b43290af3556efa1ca9.zip
Merging upstream version 10.0.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/general/plugins/callback')
-rw-r--r--ansible_collections/community/general/plugins/callback/opentelemetry.py94
-rw-r--r--ansible_collections/community/general/plugins/callback/timestamp.py127
-rw-r--r--ansible_collections/community/general/plugins/callback/yaml.py10
3 files changed, 220 insertions, 11 deletions
diff --git a/ansible_collections/community/general/plugins/callback/opentelemetry.py b/ansible_collections/community/general/plugins/callback/opentelemetry.py
index 492e42071..58cfa057b 100644
--- a/ansible_collections/community/general/plugins/callback/opentelemetry.py
+++ b/ansible_collections/community/general/plugins/callback/opentelemetry.py
@@ -84,6 +84,33 @@ DOCUMENTATION = '''
- section: callback_opentelemetry
key: disable_attributes_in_logs
version_added: 7.1.0
+ store_spans_in_file:
+ default: None
+ type: str
+ description:
+ - It stores the exported spans in the given file
+ env:
+ - name: ANSIBLE_OPENTELEMETRY_STORE_SPANS_IN_FILE
+ ini:
+ - section: callback_opentelemetry
+ key: store_spans_in_file
+ version_added: 9.0.0
+ otel_exporter_otlp_traces_protocol:
+ type: str
+ description:
+ - E(OTEL_EXPORTER_OTLP_TRACES_PROTOCOL) represents the the transport protocol for spans.
+ - See
+ U(https://opentelemetry-python.readthedocs.io/en/latest/sdk/environment_variables.html#envvar-OTEL_EXPORTER_OTLP_TRACES_PROTOCOL).
+ default: grpc
+ choices:
+ - grpc
+ - http/protobuf
+ env:
+ - name: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
+ ini:
+ - section: callback_opentelemetry
+ key: otel_exporter_otlp_traces_protocol
+ version_added: 9.0.0
requirements:
- opentelemetry-api (Python library)
- opentelemetry-exporter-otlp (Python library)
@@ -107,6 +134,7 @@ examples: |
'''
import getpass
+import json
import os
import socket
import sys
@@ -124,15 +152,19 @@ from ansible.plugins.callback import CallbackBase
try:
from opentelemetry import trace
from opentelemetry.trace import SpanKind
- from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
+ from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter as GRPCOTLPSpanExporter
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter as HTTPOTLPSpanExporter
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
- BatchSpanProcessor
+ BatchSpanProcessor,
+ SimpleSpanProcessor
+ )
+ from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
+ InMemorySpanExporter
)
-
# Support for opentelemetry-api <= 1.12
try:
from opentelemetry.util._time import _time_ns
@@ -255,7 +287,16 @@ class OpenTelemetrySource(object):
task.dump = dump
task.add_host(HostData(host_uuid, host_name, status, result))
- def generate_distributed_traces(self, otel_service_name, ansible_playbook, tasks_data, status, traceparent, disable_logs, disable_attributes_in_logs):
+ def generate_distributed_traces(self,
+ otel_service_name,
+ ansible_playbook,
+ tasks_data,
+ status,
+ traceparent,
+ disable_logs,
+ disable_attributes_in_logs,
+ otel_exporter_otlp_traces_protocol,
+ store_spans_in_file):
""" generate distributed traces from the collected TaskData and HostData """
tasks = []
@@ -271,7 +312,16 @@ class OpenTelemetrySource(object):
)
)
- processor = BatchSpanProcessor(OTLPSpanExporter())
+ otel_exporter = None
+ if store_spans_in_file:
+ otel_exporter = InMemorySpanExporter()
+ processor = SimpleSpanProcessor(otel_exporter)
+ else:
+ if otel_exporter_otlp_traces_protocol == 'grpc':
+ otel_exporter = GRPCOTLPSpanExporter()
+ else:
+ otel_exporter = HTTPOTLPSpanExporter()
+ processor = BatchSpanProcessor(otel_exporter)
trace.get_tracer_provider().add_span_processor(processor)
@@ -293,6 +343,8 @@ class OpenTelemetrySource(object):
with tracer.start_as_current_span(task.name, start_time=task.start, end_on_exit=False) as span:
self.update_span_data(task, host_data, span, disable_logs, disable_attributes_in_logs)
+ return otel_exporter
+
def update_span_data(self, task_data, host_data, span, disable_logs, disable_attributes_in_logs):
""" update the span with the given TaskData and HostData """
@@ -350,7 +402,8 @@ class OpenTelemetrySource(object):
if not disable_logs:
# This will avoid populating span attributes to the logs
span.add_event(task_data.dump, attributes={} if disable_attributes_in_logs else attributes)
- span.end(end_time=host_data.finish)
+ # Close span always
+ span.end(end_time=host_data.finish)
def set_span_attributes(self, span, attributes):
""" update the span attributes with the given attributes if not None """
@@ -462,6 +515,8 @@ class CallbackModule(CallbackBase):
self.errors = 0
self.disabled = False
self.traceparent = False
+ self.store_spans_in_file = False
+ self.otel_exporter_otlp_traces_protocol = None
if OTEL_LIBRARY_IMPORT_ERROR:
raise_from(
@@ -489,6 +544,8 @@ class CallbackModule(CallbackBase):
self.disable_logs = self.get_option('disable_logs')
+ self.store_spans_in_file = self.get_option('store_spans_in_file')
+
self.otel_service_name = self.get_option('otel_service_name')
if not self.otel_service_name:
@@ -497,6 +554,14 @@ class CallbackModule(CallbackBase):
# See https://github.com/open-telemetry/opentelemetry-specification/issues/740
self.traceparent = self.get_option('traceparent')
+ self.otel_exporter_otlp_traces_protocol = self.get_option('otel_exporter_otlp_traces_protocol')
+
+ def dump_results(self, result):
+ """ dump the results if disable_logs is not enabled """
+ if self.disable_logs:
+ return ""
+ return self._dump_results(result._result)
+
def v2_playbook_on_start(self, playbook):
self.ansible_playbook = basename(playbook._file_name)
@@ -546,7 +611,7 @@ class CallbackModule(CallbackBase):
self.tasks_data,
status,
result,
- self._dump_results(result._result)
+ self.dump_results(result)
)
def v2_runner_on_ok(self, result):
@@ -554,7 +619,7 @@ class CallbackModule(CallbackBase):
self.tasks_data,
'ok',
result,
- self._dump_results(result._result)
+ self.dump_results(result)
)
def v2_runner_on_skipped(self, result):
@@ -562,7 +627,7 @@ class CallbackModule(CallbackBase):
self.tasks_data,
'skipped',
result,
- self._dump_results(result._result)
+ self.dump_results(result)
)
def v2_playbook_on_include(self, included_file):
@@ -578,15 +643,22 @@ class CallbackModule(CallbackBase):
status = Status(status_code=StatusCode.OK)
else:
status = Status(status_code=StatusCode.ERROR)
- self.opentelemetry.generate_distributed_traces(
+ otel_exporter = self.opentelemetry.generate_distributed_traces(
self.otel_service_name,
self.ansible_playbook,
self.tasks_data,
status,
self.traceparent,
self.disable_logs,
- self.disable_attributes_in_logs
+ self.disable_attributes_in_logs,
+ self.otel_exporter_otlp_traces_protocol,
+ self.store_spans_in_file
)
+ if self.store_spans_in_file:
+ spans = [json.loads(span.to_json()) for span in otel_exporter.get_finished_spans()]
+ with open(self.store_spans_in_file, "w", encoding="utf-8") as output:
+ json.dump({"spans": spans}, output, indent=4)
+
def v2_runner_on_async_failed(self, result, **kwargs):
self.errors += 1
diff --git a/ansible_collections/community/general/plugins/callback/timestamp.py b/ansible_collections/community/general/plugins/callback/timestamp.py
new file mode 100644
index 000000000..07cd8d239
--- /dev/null
+++ b/ansible_collections/community/general/plugins/callback/timestamp.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024, kurokobo <kurokobo@protonmail.com>
+# Copyright (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
+# 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 = r"""
+ name: timestamp
+ type: stdout
+ short_description: Adds simple timestamp for each header
+ version_added: 9.0.0
+ description:
+ - This callback adds simple timestamp for each header.
+ author: kurokobo (@kurokobo)
+ options:
+ timezone:
+ description:
+ - Timezone to use for the timestamp in IANA time zone format.
+ - For example C(America/New_York), C(Asia/Tokyo)). Ignored on Python < 3.9.
+ ini:
+ - section: callback_timestamp
+ key: timezone
+ env:
+ - name: ANSIBLE_CALLBACK_TIMESTAMP_TIMEZONE
+ type: string
+ format_string:
+ description:
+ - Format of the timestamp shown to user in 1989 C standard format.
+ - >
+ Refer to L(the Python documentation,https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes)
+ for the available format codes.
+ ini:
+ - section: callback_timestamp
+ key: format_string
+ env:
+ - name: ANSIBLE_CALLBACK_TIMESTAMP_FORMAT_STRING
+ default: "%H:%M:%S"
+ type: string
+ seealso:
+ - plugin: ansible.posix.profile_tasks
+ plugin_type: callback
+ description: >
+ You can use P(ansible.posix.profile_tasks#callback) callback plugin to time individual tasks and overall execution time
+ with detailed timestamps.
+ extends_documentation_fragment:
+ - ansible.builtin.default_callback
+ - ansible.builtin.result_format_callback
+"""
+
+
+from ansible.plugins.callback.default import CallbackModule as Default
+from ansible.utils.display import get_text_width
+from ansible.module_utils.common.text.converters import to_text
+from datetime import datetime
+import types
+import sys
+
+# Store whether the zoneinfo module is available
+_ZONEINFO_AVAILABLE = sys.version_info >= (3, 9)
+
+
+def get_datetime_now(tz):
+ """
+ Returns the current timestamp with the specified timezone
+ """
+ return datetime.now(tz=tz)
+
+
+def banner(self, msg, color=None, cows=True):
+ """
+ Prints a header-looking line with cowsay or stars with length depending on terminal width (3 minimum) with trailing timestamp
+
+ Based on the banner method of Display class from ansible.utils.display
+
+ https://github.com/ansible/ansible/blob/4403519afe89138042108e237aef317fd5f09c33/lib/ansible/utils/display.py#L511
+ """
+ timestamp = get_datetime_now(self.timestamp_tzinfo).strftime(self.timestamp_format_string)
+ timestamp_len = get_text_width(timestamp) + 1 # +1 for leading space
+
+ msg = to_text(msg)
+ if self.b_cowsay and cows:
+ try:
+ self.banner_cowsay("%s @ %s" % (msg, timestamp))
+ return
+ except OSError:
+ self.warning("somebody cleverly deleted cowsay or something during the PB run. heh.")
+
+ msg = msg.strip()
+ try:
+ star_len = self.columns - get_text_width(msg) - timestamp_len
+ except EnvironmentError:
+ star_len = self.columns - len(msg) - timestamp_len
+ if star_len <= 3:
+ star_len = 3
+ stars = "*" * star_len
+ self.display("\n%s %s %s" % (msg, stars, timestamp), color=color)
+
+
+class CallbackModule(Default):
+ CALLBACK_VERSION = 2.0
+ CALLBACK_TYPE = "stdout"
+ CALLBACK_NAME = "community.general.timestamp"
+
+ def __init__(self):
+ super(CallbackModule, self).__init__()
+
+ # Replace the banner method of the display object with the custom one
+ self._display.banner = types.MethodType(banner, self._display)
+
+ 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)
+
+ # Store zoneinfo for specified timezone if available
+ tzinfo = None
+ if _ZONEINFO_AVAILABLE and self.get_option("timezone"):
+ from zoneinfo import ZoneInfo
+
+ tzinfo = ZoneInfo(self.get_option("timezone"))
+
+ # Inject options into the display object
+ setattr(self._display, "timestamp_tzinfo", tzinfo)
+ setattr(self._display, "timestamp_format_string", self.get_option("format_string"))
diff --git a/ansible_collections/community/general/plugins/callback/yaml.py b/ansible_collections/community/general/plugins/callback/yaml.py
index ae2c8f881..e41f69ec5 100644
--- a/ansible_collections/community/general/plugins/callback/yaml.py
+++ b/ansible_collections/community/general/plugins/callback/yaml.py
@@ -19,6 +19,16 @@ DOCUMENTATION = '''
- default_callback
requirements:
- set as stdout in configuration
+ seealso:
+ - plugin: ansible.builtin.default
+ plugin_type: callback
+ description: >
+ There is a parameter O(ansible.builtin.default#callback:result_format) in P(ansible.builtin.default#callback)
+ that allows you to change the output format to YAML.
+ notes:
+ - >
+ With ansible-core 2.13 or newer, you can instead specify V(yaml) for the parameter O(ansible.builtin.default#callback:result_format)
+ in P(ansible.builtin.default#callback).
'''
import yaml