Skip to content

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] - #334

Merged
richm merged 2 commits into
mainfrom
fingerprint-write-to-file
Aug 6, 2026
Merged

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]#334
richm merged 2 commits into
mainfrom
fingerprint-write-to-file

Conversation

@spetrosi

@spetrosi spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl

@spetrosi spetrosi self-assigned this Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Ignore keyword(s) in the title.

⛔ Ignored keywords (1)
  • [citest_skip]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ff1d1f9-e115-4923-884c-3351a6a95e26

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The module now collects structured role fingerprints, formats them for syslog, and optionally writes locked, trimmed JSONL records. The interface replaces sr_message with role, status, host, distribution, and logging options. Tests cover collection, formatting, persistence, check mode, validation, and failures.

Structured fingerprint logging

Layer / File(s) Summary
Fingerprint contract and collection
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
Defines structured module options, collects fingerprint fields, and formats records for syslog and JSONL output.
JSONL persistence and trimming
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
Creates parent directories, appends JSONL records under a lock, and trims old records when the size limit is exceeded.
Handler execution and validation
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
Processes structured arguments, returns check-mode data, writes records during normal execution, and reports invalid sizes or write failures.

Suggested reviewers: natoscott, richm, sfeifer

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description Format ⚠️ Warning The description contains Reason, Result, and a valid Signed-off-by line, but it lacks the required Enhancement: or Feature: section shown in .github/pull_request_template.md. Add an Enhancement: or Feature: section that describes the change, before the existing Reason: section.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits format and accurately describes the addition of JSONL fingerprint logging.
Description check ✅ Passed The description explains the feature, reason, and result, but it omits the template's Enhancement heading and issue tracker section.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
tests/unit/test_sr_fingerprint.py (4)

172-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard the cleanup so it cannot mask a test failure.

If _write_jsonl_log raises before it creates subdir, os.listdir(subdir) in the finally block raises FileNotFoundError. That error replaces the original failure and hides the cause. The other tests in this file avoid the problem by using _cleanup_log, which swallows OSError.

♻️ Proposed change
         finally:
             subdir = os.path.dirname(log_file)
-            for name in os.listdir(subdir):
-                os.unlink(os.path.join(subdir, name))
-            os.rmdir(subdir)
-            os.rmdir(tmpdir)
+            shutil.rmtree(tmpdir, ignore_errors=True)

This needs import shutil at the top of the file. shutil.rmtree also removes the .lock sidecar.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 172 - 177, Update the finally
cleanup in the test around _write_jsonl_log to use shutil.rmtree on the
temporary directory, adding the required shutil import, so missing
subdirectories do not mask the original test failure and the .lock sidecar is
removed.

131-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the Black formatting, and cover the remaining escape branches.

Two points:

  1. Lines 133-135 wrap the assignment in parentheses. The single-line form is about 80 characters, which is under the Black default of 88, so tox -e black collapses it and reports this file as unformatted. Run tox -e black,flake8 as required by the path instructions.
  2. _format_fingerprint_key_value has three output branches. This test covers only the space branch. The =-in-value branch and the quote-escaping branch are untested. Those branches decide how downstream key=value parsers read the record.
♻️ Proposed change
     def test_format_fingerprint_syslog_quotes_values_with_spaces(self):
         record = _sample_fingerprint_record()
-        record["role_path"] = (
-            "/usr/share/ansible/roles/linux-system-roles.systemd extra"
-        )
+        record["role_path"] = "/usr/share/ansible/roles/linux-system-roles.systemd extra"
         message = sr_fingerprint._format_fingerprint_syslog(record)
         self.assertIn(
             'role_path="/usr/share/ansible/roles/linux-system-roles.systemd extra"',
             message,
         )
+
+    def test_format_fingerprint_key_value_quotes_equals_and_escapes_quotes(self):
+        self.assertEqual(
+            sr_fingerprint._format_fingerprint_key_value("role_name", "a=b"),
+            'role_name="a=b"',
+        )
+        # Update the expected text if the escape convention changes.
+        self.assertEqual(
+            sr_fingerprint._format_fingerprint_key_value("role_name", 'a"b'),
+            'role_name="a""b"',
+        )
+
+    def test_format_fingerprint_key_value_handles_none(self):
+        self.assertEqual(
+            sr_fingerprint._format_fingerprint_key_value("role_name", None),
+            "role_name=",
+        )

The replacement line for role_path is 82 characters, which stays under the Black limit.

As per path instructions: "Must follow PEP 8 and be formatted with Python Black" and "Run tox -e black,flake8 before committing".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 131 - 140, Update
test_format_fingerprint_syslog_quotes_values_with_spaces to use Black’s
single-line assignment for role_path, then add focused assertions covering
_format_fingerprint_key_value values containing “=” and embedded quotes,
verifying the expected escaping and parser-safe output. Run tox -e black,flake8
to validate formatting and linting.

Source: Path instructions


195-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test that the trim preserves the file mode.

_trim_log_file replaces the log through tempfile.mkstemp plus os.rename, and restores the mode with os.fchmod. mkstemp creates files with mode 0600, so a regression in that fchmod call silently changes the permissions of the live log. No test covers it.

♻️ Proposed test
    def test_trim_preserves_file_mode(self):
        with tempfile.NamedTemporaryFile(delete=False, suffix=".jsonl") as tmp:
            log_file = tmp.name

        try:
            os.chmod(log_file, 0o640)
            record = _sample_fingerprint_record()
            line_size = len(sr_fingerprint._format_fingerprint_jsonl(record) + "\n")
            max_size = line_size * 3
            for _i in range(6):
                sr_fingerprint._write_jsonl_log(log_file, record, max_size=max_size)

            mode = stat.S_IMODE(os.stat(log_file).st_mode)
            self.assertEqual(mode, 0o640)
        finally:
            _cleanup_log(log_file)

This needs import stat at the top of the file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 195 - 219, Add import stat
and add a test method test_trim_preserves_file_mode alongside
test_trim_removes_oldest_lines. Set the log file to mode 0o640, write enough
records through _write_jsonl_log to trigger _trim_log_file, then assert
stat.S_IMODE(os.stat(log_file).st_mode) remains 0o640 and clean up the temporary
file in finally.

306-402: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the normal-execution success path.

The four handler tests cover check mode, the write failure, and the validation failure. No test covers the main path this PR adds: write_log_file=true outside check mode, where _handle_fingerprint calls module.log and then appends a real line to the log file. _FakeModule.logged exists at line 36 but no test reads it.

♻️ Proposed test
    def test_handle_fingerprint_writes_log_and_syslog(self):
        log_path = os.path.join(tempfile.mkdtemp(), "fingerprint.jsonl")
        module = _FakeModule(
            {
                "status": "success",
                "write_log_file": True,
                "log_file": log_path,
                "max_log_size": 2000000,
                "role_name": "systemd",
                "role_path": "/usr/share/ansible/roles/linux-system-roles.systemd",
                "ansible_play_hosts_all": ["host1", "host2"],
                "distribution": "RedHat",
                "distribution_version": "9.4",
            },
            check_mode=False,
        )
        try:
            with self.assertRaises(_ExitJsonException) as ctx:
                sr_fingerprint._handle_fingerprint(module)

            result = ctx.exception.kwargs
            self.assertFalse(result["changed"])
            self.assertEqual(result["fingerprint"]["status"], "success")
            self.assertNotIn("jsonl_row", result)

            self.assertEqual(len(module.logged), 1)
            self.assertIn("role_name=systemd", module.logged[0])
            self.assertIn("status=success", module.logged[0])

            with open(log_file_dir_safe(log_path), "r") as log_fd:
                lines = log_fd.read().splitlines()
            self.assertEqual(len(lines), 1)
            parsed = json.loads(lines[0])
            self.assertEqual(parsed, result["fingerprint"])
            self.assertEqual(parsed["play_hosts_number"], 2)
            self.assertFalse(parsed["ansible_check_mode"])
        finally:
            shutil.rmtree(os.path.dirname(log_path), ignore_errors=True)

Replace log_file_dir_safe(log_path) with log_path; that placeholder is not needed. The test also needs import shutil. It asserts the two outputs stay consistent: the syslog text and the JSONL record derive from the same fingerprint that exit_json returns.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` around lines 306 - 402, Add a
normal-execution success test near the existing _handle_fingerprint tests, using
write_log_file=True and a temporary log path. Assert _ExitJsonException returns
unchanged status and no jsonl_row, verify module.logged contains one entry with
the role and status, then read log_path directly and confirm the single JSONL
record equals result["fingerprint"] and includes two hosts with
ansible_check_mode false. Import shutil and clean up the temporary directory in
a finally block.
library/sr_fingerprint.py (3)

220-237: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Create the log file and lock sidecar with restrictive permissions.

open(log_file, "a") and open(lock_path, "w") create new files with 0666 & ~umask, which is normally 0644. The log records role names, role paths, host counts, and the managed node distribution for every play. Other log files under /var/log normally use 0600 or 0640. Set the mode explicitly on creation so the umask of the Ansible connection does not decide it.

♻️ Proposed change to set an explicit mode
 def _write_jsonl_log(log_file, record, max_size=0):
     _ensure_parent_dir(log_file)
     new_line = _format_fingerprint_jsonl(record) + "\n"
     lock_path = log_file + ".lock"
-    lock_fd = open(lock_path, "w")
+    lock_fd = os.fdopen(
+        os.open(lock_path, os.O_WRONLY | os.O_CREAT, 0o600), "w"
+    )
     try:
         fcntl.flock(lock_fd, fcntl.LOCK_EX)
         try:
             cur_size = os.path.getsize(log_file)
         except OSError:
             cur_size = 0
         if max_size > 0 and cur_size + len(new_line) > max_size and cur_size > 0:
             _trim_log_file(log_file, len(new_line))
-        with open(log_file, "a") as log_fd:
+        with os.fdopen(
+            os.open(log_file, os.O_WRONLY | os.O_APPEND | os.O_CREAT, 0o600), "a"
+        ) as log_fd:
             log_fd.write(new_line)

Note that _trim_log_file preserves the existing mode through os.fchmod, so the mode set at creation stays stable across trims.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 220 - 237, Update _write_jsonl_log to
create both log_file and lock_path with explicitly restrictive permissions, such
as 0600, rather than relying on the process umask. Apply the mode only when each
file is first created, preserving existing permissions for already-created files
and the current locking, writing, and trimming behavior.

328-331: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Widen the exception handling around the log write.

In Python 3, IOError is an alias of OSError, so this tuple catches only OSError. _trim_log_file opens the existing log in text mode without an explicit encoding. If the log holds bytes that the locale codec cannot decode, readlines() raises UnicodeDecodeError, which derives from ValueError. The module then aborts with a traceback instead of a clean fail_json.

♻️ Proposed change
-        except (IOError, OSError) as exc:
+        except (OSError, ValueError) as exc:
             module.fail_json(
                 msg="Failed to write fingerprint log file %s: %s" % (log_file, exc)
             )

Opening the file in _trim_log_file with an explicit encoding="utf-8" and errors="replace" would also remove the decode failure at the source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 328 - 331, Widen the exception
handling around the `_trim_log_file` log-write path to include
`UnicodeDecodeError` (or its appropriate `ValueError` base) so decode failures
reach `module.fail_json` with the existing file context. Also open the trimmed
log with explicit UTF-8 encoding and replacement error handling to prevent
locale-dependent decode failures.

283-287: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Escape control characters, and reconsider the quote escape convention.

Two points about _format_fingerprint_key_value:

  1. No field strips or escapes newlines. If any value contains \n, module.log() emits a record that a syslog reader splits into two lines. A crafted role_name or role_path can then forge a second fingerprint entry. The values originate from role variables today, so the risk is low, but the guard is cheap.
  2. The function escapes " by doubling it. The common convention for key=value syslog text, including logfmt and rsyslog field parsers, is backslash escaping. A value with a quote becomes role_name="a""b", which most key=value parsers read as the value a followed by a parse error. The PR targets upstream consumers of these messages, so the escape convention affects them.
♻️ Proposed change
 def _format_fingerprint_key_value(field, value):
     text = "" if value is None else str(value)
+    text = text.replace("\\", "\\\\")
+    for char, escape in (("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t")):
+        text = text.replace(char, escape)
     if any(char in text for char in ' "='):
-        return '%s="%s"' % (field, text.replace('"', '""'))
+        return '%s="%s"' % (field, text.replace('"', '\\"'))
     return "%s=%s" % (field, text)

If the doubling convention is deliberate and matches an existing downstream parser, keep it and add a comment that records the parser. Update test_format_fingerprint_syslog_quotes_values_with_spaces in tests/unit/test_sr_fingerprint.py and add a case for a value that contains a quote and a newline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@library/sr_fingerprint.py` around lines 283 - 287, Update
_format_fingerprint_key_value to escape control characters, including newlines,
so values cannot inject additional syslog records, and use backslash escaping
for embedded quotes to match downstream key=value parsers. Update
test_format_fingerprint_syslog_quotes_values_with_spaces and add coverage for
values containing both a quote and a newline; if quote doubling is intentionally
required by an existing parser, preserve it and document that parser instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/test_sr_fingerprint.py`:
- Line 17: Configure the test environment so pytest can resolve the library
directory when collecting tests/unit/test_sr_fingerprint.py and importing
sr_fingerprint. Add the repository’s established pytest, conftest.py, or tox
path configuration for library/, preserving the existing test import without
changing the test module itself.

---

Nitpick comments:
In `@library/sr_fingerprint.py`:
- Around line 220-237: Update _write_jsonl_log to create both log_file and
lock_path with explicitly restrictive permissions, such as 0600, rather than
relying on the process umask. Apply the mode only when each file is first
created, preserving existing permissions for already-created files and the
current locking, writing, and trimming behavior.
- Around line 328-331: Widen the exception handling around the `_trim_log_file`
log-write path to include `UnicodeDecodeError` (or its appropriate `ValueError`
base) so decode failures reach `module.fail_json` with the existing file
context. Also open the trimmed log with explicit UTF-8 encoding and replacement
error handling to prevent locale-dependent decode failures.
- Around line 283-287: Update _format_fingerprint_key_value to escape control
characters, including newlines, so values cannot inject additional syslog
records, and use backslash escaping for embedded quotes to match downstream
key=value parsers. Update
test_format_fingerprint_syslog_quotes_values_with_spaces and add coverage for
values containing both a quote and a newline; if quote doubling is intentionally
required by an existing parser, preserve it and document that parser instead.

In `@tests/unit/test_sr_fingerprint.py`:
- Around line 172-177: Update the finally cleanup in the test around
_write_jsonl_log to use shutil.rmtree on the temporary directory, adding the
required shutil import, so missing subdirectories do not mask the original test
failure and the .lock sidecar is removed.
- Around line 131-140: Update
test_format_fingerprint_syslog_quotes_values_with_spaces to use Black’s
single-line assignment for role_path, then add focused assertions covering
_format_fingerprint_key_value values containing “=” and embedded quotes,
verifying the expected escaping and parser-safe output. Run tox -e black,flake8
to validate formatting and linting.
- Around line 195-219: Add import stat and add a test method
test_trim_preserves_file_mode alongside test_trim_removes_oldest_lines. Set the
log file to mode 0o640, write enough records through _write_jsonl_log to trigger
_trim_log_file, then assert stat.S_IMODE(os.stat(log_file).st_mode) remains
0o640 and clean up the temporary file in finally.
- Around line 306-402: Add a normal-execution success test near the existing
_handle_fingerprint tests, using write_log_file=True and a temporary log path.
Assert _ExitJsonException returns unchanged status and no jsonl_row, verify
module.logged contains one entry with the role and status, then read log_path
directly and confirm the single JSONL record equals result["fingerprint"] and
includes two hosts with ansible_check_mode false. Import shutil and clean up the
temporary directory in a finally block.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: faedb1f8-84b0-4cc5-aef8-b90c2184657d

📥 Commits

Reviewing files that changed from the base of the PR and between 85c8584 and c1024a5.

📒 Files selected for processing (2)
  • library/sr_fingerprint.py
  • tests/unit/test_sr_fingerprint.py

import tempfile
import unittest

import sr_fingerprint

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate the test runner configuration and any sys.path setup for library/.
fd -H -t f -g 'tox.ini' -g 'pytest.ini' -g 'setup.cfg' -g 'pyproject.toml' -g 'conftest.py' .

echo "--- path setup references ---"
rg -n 'PYTHONPATH|pythonpath|sys\.path|rootdir|testpaths|library' \
  -g 'tox.ini' -g 'pytest.ini' -g 'setup.cfg' -g 'pyproject.toml' -g 'conftest.py' \
  -g '.github/workflows/*' .

Repository: linux-system-roles/metrics

Length of output: 495


🏁 Script executed:

#!/bin/bash
set -u

python3 - <<'PY'
import ast
from pathlib import Path

for filename in ['tests/unit/test_sr_fingerprint.py', 'tox.ini']:
    p = Path(filename)
    print(f'--- {filename}: exists={p.exists()}, size={p.stat().st_size if p.exists() else None} ---')
    if p.exists():
        print(p.read_text())
        print()

# Locate pytest-related files and inspect relevant contents without executing repo code.
for p in sorted(Path('.').rglob('*')):
    if p.is_file() and p.name in {'pytest.ini', 'setup.cfg', 'pyproject.toml', 'conftest.py'}:
        try:
            text = p.read_text(errors='replace')
        except Exception:
            continue
        if any(s in text for s in ['pythonpath', 'python_paths', 'rootdir', 'testpaths', 'collect_ignore', 'addopts', 'sys.path', 'PYTHONPATH', 'library', 'sr_fingerprint']):
            print(f'--- potential config: {p} ---')
            print(text)
</PY

echo "--- direct pytest/action workflow files ---"
git ls-files | rg '(^|/)(tox\.ini|pytest\.ini|setup\.cfg|pyproject\.toml|\.github/workflows/.*\.ya?ml|conftest\.py)$' || true

echo "--- search for sys.path setup and python package paths ---"
rg -n -C 2 'sys\.path|pythonpath|PYTHONPATH|python_paths|rootdir|testpaths|collect_ignore|library/st*|sr_fingerprint' \
  -g 'tox.ini' -g 'pytest.ini' -g 'setup.cfg' -g 'pyproject.toml' \
  -g '.github/workflows/*' \
  -g 'conftest.py' \
  .

echo "--- tox actions/scripts ---"
sed -n '1,220p' tox.ini 2>/dev/null || true

Repository: linux-system-roles/metrics

Length of output: 320


🏁 Script executed:

#!/bin/bash
set -u

echo "--- tracked pytest/config/workflow files ---"
git ls-files | rg '(^|/)(tox\.ini|pytest\.ini|setup\.cfg|pyproject\.toml|conftest\.py$|\.github/workflows/.*\.ya?ml)$' || true

echo "--- tox.ini contents ---"
if [ -f tox.ini ]; then
  sed -n '1,240p' tox.ini
else
  echo "tox.ini not found"
fi

echo "--- pytest.ini, setup.cfg, pyproject.toml contents ---"
for f in pytest.ini setup.cfg pyproject.toml; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,240p' "$f"
  else
    echo "--- $f not found ---"
  fi
done

echo "--- conftest contents ---"
if [ -f conftest.py ]; then
  sed -n '1,240p' conftest.py
else
  echo "conftest.py not found"
fi

echo "--- path setup references in config/workflows/context ---"
rg -n -C 2 'sys\.path|sys-path|pythonpath|python_paths|PYTHONPATH|rootdir|testpaths|collect_ignore|library|sr_fingerprint|pytest' \
  tox.ini pytest.ini setup.cfg pyproject.toml conftest.py .github/workflows . 2>/dev/null | head -n 240 || true

Repository: linux-system-roles/metrics

Length of output: 18123


🏁 Script executed:

#!/bin/bash
set -u

echo "--- .github/workflows files referencing tox/pytest/ansible-test/pythonpath ---"
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
  [ -f "$f" ] || continue
  if rg -q 'tox|pytest|ansible-test|pythonpath|PYTHONPATH|lsr_enable|lsr_unit|unit' "$f"; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

echo "--- read-only import lookup simulation for tests unit runner cwd scenarios ---"
python3 - <<'EOF'
from pathlib import Path
root = Path.cwd()
examples = [
    ("current", [root]),
    ("pytest tests/unit", [root / "tests" / "unit"]),
    ("pytest tests", [root / "tests"]),
]
for name, path_list in examples:
    import sys
    sys.path[:] = [str(p) for p in path_list]
    try:
        import importlib.util
        spec = importlib.util.find_spec("sr_fingerprint")
        print(f"{name}: found={bool(spec)}")
    except ModuleNotFoundError:
        print("current: found=False")
EOF

Repository: linux-system-roles/metrics

Length of output: 16321


Add a pytest import path for library/sr_fingerprint.

tests/unit/test_sr_fingerprint.py imports sr_fingerprint, but this repository does not include a pytest.ini, setup.cfg, pyproject.toml, conftest.py, or tox/workflow path setup for library/. Add a pytest pythonpath/python_paths entry, sys.path setup in conftest.py, or a tox path variable so collection does not fail when running the import under tests/unit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_sr_fingerprint.py` at line 17, Configure the test environment
so pytest can resolve the library directory when collecting
tests/unit/test_sr_fingerprint.py and importing sr_fingerprint. Add the
repository’s established pytest, conftest.py, or tox path configuration for
library/, preserving the existing test import without changing the test module
itself.

Source: Path instructions

@spetrosi spetrosi changed the title feat: Write roles fingerprints to /var/log/sysroles.jsonl feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip] Aug 6, 2026
Feature: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]

Reason: By default logs are printed to rsyslog. This change adds a possibility to write logs to a file on the system for the downstream users.

Result: For the upstream, this makes rsyslog log message more detailed. For the downstream - also writes logs to /var/log/sysroles.jsonl
Signed-off-by: Sergei Petrosian <spetrosi@redhat.com>
@spetrosi
spetrosi force-pushed the fingerprint-write-to-file branch from c1024a5 to 3dc5059 Compare August 6, 2026 15:04
The sr_fingerprint module was rewritten to accept structured parameters
(status, role_name, role_path, etc.) instead of a free-form sr_message.
Update the role tasks and tests to match the new module interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@spetrosi

spetrosi commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

[citest]

@richm
richm merged commit 063e7a6 into main Aug 6, 2026
10 of 11 checks passed
@richm
richm deleted the fingerprint-write-to-file branch August 6, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants