summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/callback/timestamp.py
blob: 07cd8d239caa80eea7a1d67e061060145a48b44d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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"))