feat: add role fingerprints to syslog

Feature: Add a fingerprint string to the system log to indicate when the role began
successfully, and when the role finished successfully.  The fingerprint string indicates
the role name, a timestamp, and the platform.

Reason: Users can see when the role was used and if it was used successfully.  This
information from the system log can be collected by log scanners and aggregators
for further analysis.

Result: The role logs fingerprints to the system log.

This also adds a test to check if the fingerprints were written upon a successful
role invocation.

Signed-off-by: Rich Megginson <rmeggins@redhat.com>
This commit is contained in:
Rich Megginson 2026-04-27 08:01:14 -06:00 committed by Richard Megginson
parent 09f4336dee
commit b94f3d0993
17 changed files with 138 additions and 0 deletions

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

View file

@ -1,3 +1,4 @@
plugins/modules/network_connections.py validate-modules:missing-gplv3-license
plugins/modules/network_state.py validate-modules:missing-gplv3-license
plugins/modules/sr_fingerprint.py validate-modules:missing-gplv3-license
tests/network/ensure_provider_tests.py shebang!skip

88
library/sr_fingerprint.py Normal file
View file

@ -0,0 +1,88 @@
#!/usr/bin/python
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
module: sr_fingerprint
short_description: Write a message string to syslog using Ansible C(module.log) function.
description:
- Writes the given string to the system log using Ansible C(module.log) function.
- Intended for role-internal or diagnostic use.
author: Rich Megginson (@richm)
options:
sr_message:
description: Text to record in syslog.
type: str
required: true
"""
EXAMPLES = """
- name: Record a fingerprint message in syslog
sr_fingerprint:
sr_message: "system_role:ROLENAME"
"""
RETURN = r""" # """
from ansible.module_utils.basic import AnsibleModule
import datetime
def _local_iso8601_no_microseconds():
"""System local wall clock with local tz offset, ISO 8601, seconds only."""
try:
utc = datetime.timezone.utc
except AttributeError:
import time
return time.strftime("%Y-%m-%dT%H:%M:%S%z", time.localtime())
# Prefer the local clock interpreted in the system timezone (not UTC displayed).
now = datetime.datetime.now()
astimezone = getattr(now, "astimezone", None)
if astimezone is not None:
try:
return astimezone().replace(microsecond=0).isoformat()
except (OSError, TypeError, ValueError):
pass
return datetime.datetime.now(utc).astimezone().replace(microsecond=0).isoformat()
def run_module():
module_args = dict(
sr_message=dict(type="str", required=True),
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True,
)
log_message = "%s %s" % (
module.params["sr_message"],
_local_iso8601_no_microseconds(),
)
if module.check_mode:
module.exit_json(
changed=False,
message="Check mode: message not logged - [%s]" % log_message,
)
module.log(log_message)
# we don't actually change anything, so we're not changed - writing a log message
# is not considered a change
# also, we don't want to report changed every time the role runs
module.exit_json(changed=False)
def main():
run_module()
if __name__ == "__main__":
main()

View file

@ -220,3 +220,9 @@
- name: Re-test connectivity
ping:
- name: Record role success fingerprint
sr_fingerprint:
sr_message: >-
success system_role:network ansible_version={{ ansible_version.full }}
{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}

View file

@ -7,6 +7,12 @@
difference(ansible_facts.keys() | list) | length > 0
no_log: true
- name: Record role begin fingerprint
sr_fingerprint:
sr_message: >-
begin system_role:network ansible_version={{ ansible_version.full }}
{{ ansible_facts['distribution'] }}-{{ ansible_facts['distribution_version'] }}
- name: Determine if system is ostree and set flag
when: not __network_is_ostree is defined
block:

View file

@ -3,12 +3,37 @@
- name: Test executing the role with default parameters
hosts: all
tasks:
- name: See if /dev/log exists for the fingerprint check
ansible.builtin.stat:
path: /dev/log
register: __register_dev_log
- name: Set the start time for the journal search
ansible.builtin.set_fact:
__journal_start_time: "{{ ansible_facts['date_time']['date'] ~ ' ' ~ ansible_facts['date_time']['time'] }}"
when: __register_dev_log.stat.exists
- name: Include the task 'el_repo_setup.yml'
include_tasks: tasks/el_repo_setup.yml
- name: Run role with clear facts
include_tasks: tasks/run_role_with_clear_facts.yml
# look for the exact module invocation, not some other message that might contain the string
- name: Check system journal contains role fingerprints
ansible.builtin.shell:
executable: /bin/bash
cmd: >-
set -eo pipefail;
journalctl --since "{{ __journal_start_time }}" --no-pager |
grep -v " Invoked with" | grep "sr_fingerprint.*begin system_role:network" ||
{ echo ERROR: BEGIN fingerprint not found; exit 1; };
journalctl --since "{{ __journal_start_time }}" --no-pager |
grep -v " Invoked with" | grep "sr_fingerprint.*success system_role:network" ||
{ echo ERROR: SUCCESS fingerprint not found; exit 1; }
changed_when: false
when: __register_dev_log.stat.exists
# module_warning - see https://github.com/ansible/ansible/issues/85394
- name: Test warning and info logs
assert: