Skip to content

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

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

feat: Write roles fingerprints to /var/log/sysroles.jsonl [citest_skip]#137
richm merged 3 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 requested a review from richm as a code owner August 6, 2026 12:50
@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: 5adf8149-f71f-40f7-9467-255d33fabaf8

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

The module now collects structured role fingerprints, emits canonical syslog records, and optionally writes locked JSONL records with size trimming. It adds check-mode reporting, argument validation, write-error handling, and comprehensive unit tests.

Changes

Role fingerprint logging

Layer / File(s) Summary
Fingerprint contract and formatting
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module accepts role metadata and status, derives fingerprint fields, and formats syslog and JSONL output. Tests cover fields, helpers, quoting, and timestamps.
JSONL persistence and trimming
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module creates parent directories, appends locked JSONL records, preserves value types, and trims older records by size.
Execution and error handling
library/sr_fingerprint.py, tests/unit/test_sr_fingerprint.py
The module validates arguments, handles check mode, emits syslog output, reports log-file details, and fails on write errors. Tests cover these paths.

Suggested reviewers: richm, nhosoi

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description Format ⚠️ Warning The description has Reason, Result, and a valid Signed-off-by, but it has no required Enhancement: or Feature: section. Add an Enhancement: or Feature: section that describes the added role-fingerprint file logging and detailed rsyslog output.
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 uses the required Conventional Commits format and accurately describes the fingerprint logging change.
Description check ✅ Passed The description explains the feature, reason, and result, but it omits the exact Enhancement heading and Issue Tracker Tickets section.
✨ Finishing Touches
📝 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.

🧹 Nitpick comments (6)
library/sr_fingerprint.py (3)

322-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the validated local variable.

Line 300 already reads max_log_size from module.params. Line 326 reads the same key again. Pass the local variable to keep one source for the validated value.

♻️ Optional cleanup
-            _write_jsonl_log(
-                log_file, fingerprint_record, module.params["max_log_size"]
-            )
+            _write_jsonl_log(log_file, fingerprint_record, max_log_size)
🤖 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 322 - 327, Update the
_write_jsonl_log call in the write_log_file branch to pass the existing
validated max_log_size local variable instead of rereading
module.params["max_log_size"].

191-232: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Trim only frees the size of the new record.

_write_jsonl_log calls _trim_log_file(log_file, len(new_line)). _trim_log_file stops as soon as it removes len(new_line) bytes. If cur_size is already above max_size, for example after an operator lowers max_log_size, the file stays above the limit for many writes. Trimming to a target size instead converges in one pass.

♻️ Optional: trim to the target size
-        if max_size > 0 and cur_size + len(new_line) > max_size and cur_size > 0:
-            _trim_log_file(log_file, len(new_line))
+        if max_size > 0 and cur_size + len(new_line) > max_size and cur_size > 0:
+            _trim_log_file(log_file, cur_size + len(new_line) - max_size)

This changes the expectations in tests/unit/test_sr_fingerprint.py only when the file starts above the limit.

🤖 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 191 - 232, Update _trim_log_file and
its call from _write_jsonl_log so trimming removes enough oldest records to
bring the log within max_size after the new record is written, rather than
removing only len(new_line) bytes. Pass or calculate the required excess based
on the current size, max_size, and new record length, while preserving the
existing behavior for files already within the limit.

283-287: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Quote escaping uses the doubled-quote convention.

_format_fingerprint_key_value escapes " as "". Most key=value log parsers, including the rsyslog and Logstash kv style used for this format, expect a backslash escape (\"). A role_path or distribution string that contains a quote then parses incorrectly downstream. Confirm which convention the downstream consumers of /var/log/sysroles.jsonl and the syslog records use.

♻️ Optional: use backslash escaping
-    if any(char in text for char in ' "='):
-        return '%s="%s"' % (field, text.replace('"', '""'))
+    if any(char in text for char in ' "=\\'):
+        escaped = text.replace("\\", "\\\\").replace('"', '\\"')
+        return '%s="%s"' % (field, escaped)

If you change this, update test_format_fingerprint_syslog_quotes_values_with_spaces in tests/unit/test_sr_fingerprint.py.

🤖 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, The
_format_fingerprint_key_value function uses doubled quotes, which downstream kv
parsers may misinterpret. Confirm the escaping convention used by
/var/log/sysroles.jsonl and syslog consumers, then use backslash escaping for
embedded quotes if required; update
test_format_fingerprint_syslog_quotes_values_with_spaces to match the confirmed
behavior.
tests/unit/test_sr_fingerprint.py (3)

161-177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Cleanup can mask the original failure.

If _write_jsonl_log raises, subdir does not exist. os.listdir(subdir) in the finally block then raises and hides the real error. shutil.rmtree(tmpdir) removes the tree and the lock sidecar in one call.

♻️ Optional cleanup
         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)

Add import shutil to the imports.

🤖 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 161 - 177, Update
test_write_jsonl_log_creates_parent_dir cleanup to import shutil and replace the
manual subdir/file removal with shutil.rmtree(tmpdir), ensuring cleanup succeeds
even when _write_jsonl_log fails before creating the parent directory.

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

Add coverage for the = and " escape branches.

This test covers only a value that contains a space. _format_fingerprint_key_value also quotes values that contain = or ", and it doubles an embedded ". That escape rule has no test, so a change to the convention passes silently.

🧪 Suggested extra test
    def test_format_fingerprint_syslog_escapes_quotes_and_equals(self):
        record = _sample_fingerprint_record()
        record["role_name"] = 'ro"le=x'
        message = sr_fingerprint._format_fingerprint_syslog(record)
        self.assertIn('role_name="ro""le=x"', message)
🤖 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, Add a test
alongside test_format_fingerprint_syslog_quotes_values_with_spaces that sets
role_name to a value containing both an embedded double quote and an equals
sign, then asserts _format_fingerprint_syslog outputs the value quoted with the
embedded quote doubled, covering both escaping branches.

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

Add a handler test for the non-check-mode success path.

The handler tests cover check mode, write failure, and argument validation. No test asserts that _handle_fingerprint calls module.log and returns the fingerprint value when check_mode is false. That path is the main behavior of the module. _FakeModule.logged already records the calls.

🧪 Suggested extra test
    def test_handle_fingerprint_logs_to_syslog(self):
        module = _FakeModule(
            {
                "status": "success",
                "write_log_file": False,
                "max_log_size": 2000000,
                "role_name": "systemd",
                "role_path": "/usr/share/ansible/roles/linux-system-roles.systemd",
                "ansible_play_hosts_all": ["host1"],
                "distribution": "RedHat",
                "distribution_version": "9.4",
            },
            check_mode=False,
        )
        with self.assertRaises(_ExitJsonException) as ctx:
            sr_fingerprint._handle_fingerprint(module)
        self.assertEqual(len(module.logged), 1)
        self.assertIn("status=success", module.logged[0])
        self.assertEqual(ctx.exception.kwargs["fingerprint"]["status"], "success")
🤖 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 - 351, Add a
non-check-mode success test alongside the existing _handle_fingerprint tests,
using _FakeModule with write_log_file disabled and status success. Invoke
_handle_fingerprint, assert exactly one module.log call containing
status=success, and verify the raised result includes fingerprint["status"]
equal to success.
🤖 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.

Nitpick comments:
In `@library/sr_fingerprint.py`:
- Around line 322-327: Update the _write_jsonl_log call in the write_log_file
branch to pass the existing validated max_log_size local variable instead of
rereading module.params["max_log_size"].
- Around line 191-232: Update _trim_log_file and its call from _write_jsonl_log
so trimming removes enough oldest records to bring the log within max_size after
the new record is written, rather than removing only len(new_line) bytes. Pass
or calculate the required excess based on the current size, max_size, and new
record length, while preserving the existing behavior for files already within
the limit.
- Around line 283-287: The _format_fingerprint_key_value function uses doubled
quotes, which downstream kv parsers may misinterpret. Confirm the escaping
convention used by /var/log/sysroles.jsonl and syslog consumers, then use
backslash escaping for embedded quotes if required; update
test_format_fingerprint_syslog_quotes_values_with_spaces to match the confirmed
behavior.

In `@tests/unit/test_sr_fingerprint.py`:
- Around line 161-177: Update test_write_jsonl_log_creates_parent_dir cleanup to
import shutil and replace the manual subdir/file removal with
shutil.rmtree(tmpdir), ensuring cleanup succeeds even when _write_jsonl_log
fails before creating the parent directory.
- Around line 131-140: Add a test alongside
test_format_fingerprint_syslog_quotes_values_with_spaces that sets role_name to
a value containing both an embedded double quote and an equals sign, then
asserts _format_fingerprint_syslog outputs the value quoted with the embedded
quote doubled, covering both escaping branches.
- Around line 306-351: Add a non-check-mode success test alongside the existing
_handle_fingerprint tests, using _FakeModule with write_log_file disabled and
status success. Invoke _handle_fingerprint, assert exactly one module.log call
containing status=success, and verify the raised result includes
fingerprint["status"] equal to success.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d80b59a5-34ce-4d08-bd66-0466e83c0ede

📥 Commits

Reviewing files that changed from the base of the PR and between 6d88f7e and 1937b96.

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

@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 1937b96 to f4fd91e Compare August 6, 2026 15:11
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]

The fingerprint commit inadvertently replaced this file with a minimal
template, losing role-specific test dependencies. Restore the original.

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 7456960 into main Aug 6, 2026
13 checks passed
@richm
richm deleted the fingerprint-write-to-file branch August 6, 2026 22:05
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