Visitar URL original
feat(postgres): add Restic remote backups and restore validation by Lebowski89 · Pull Request #563 · Lebowski89/homelab · GitHub
Skip to content

feat(postgres): add Restic remote backups and restore validation - #563

Merged
Lebowski89 merged 4 commits into
mainfrom
qol/branch2
Aug 12, 2026
Merged

Lebowski89 merged 4 commits into
mainfrom
qol/branch2

Conversation

@Lebowski89

@Lebowski89 Lebowski89 commented Aug 12, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added optional remote PostgreSQL logical backups with Restic, including repository initialization, uploads, retention maintenance, scheduling, metrics, and secure credential handling.
    • Added automated restore validation with snapshot checks, isolated database restoration, safety checks, cleanup, and scheduled execution.
    • Added Ansible tags and command mappings for backup and restore-validation operations.
  • Documentation

    • Documented configuration, commands, check mode, metrics, recovery boundaries, and restore-validation workflows.
  • Tests

    • Added comprehensive coverage for backup, restore validation, security, failure handling, systemd integration, and shell safety.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54872370-cf90-458e-9794-f562fc27b3e6

📥 Commits

Reviewing files that changed from the base of the PR and between 2aaf0ed and 6a61d0c.

📒 Files selected for processing (10)
  • ansible/roles/postgres/README.md
  • ansible/roles/postgres/defaults/main.yml
  • ansible/roles/postgres/tasks/main.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml
  • ansible/roles/postgres/templates/postgres-logical-backup-remote.sh.j2
  • ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2
  • docs/postgresql-logical-backups.md
  • tests/unit/test_postgres_remote_backup.py
  • tests/unit/test_postgres_restore_validation.py
🚧 Files skipped from review as they are similar to previous changes (9)
  • ansible/roles/postgres/defaults/main.yml
  • ansible/roles/postgres/README.md
  • ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml
  • ansible/roles/postgres/tasks/main.yml
  • tests/unit/test_postgres_remote_backup.py
  • ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml
  • ansible/roles/postgres/templates/postgres-logical-backup-remote.sh.j2
  • ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2
  • docs/postgresql-logical-backups.md

📝 Walkthrough

Walkthrough

The PostgreSQL role now supports optional Restic remote logical backups and automated restore validation. It adds configuration, secret management, systemd jobs, host-local scripts, metrics, Ansible tags, documentation, and comprehensive tests.

Changes

PostgreSQL backup and recovery

Layer / File(s) Summary
Remote backup configuration and scheduling
ansible/roles/postgres/defaults/main.yml, ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml, ansible/roles/postgres/templates/postgres-logical-backup-remote.env.j2, ansible/roles/ubuntu/templates/skynet.j2, docs/..., tests/unit/test_postgres_remote_backup.py
Adds disabled-by-default Restic settings, validation, credential resolution, protected files, managed systemd jobs, tags, documentation, and setup tests.
Remote backup execution and secret reconciliation
ansible/roles/postgres/tasks/sub_tasks/backup_remote_action.yml, ansible/roles/postgres/tasks/sub_tasks/backup_remote_secret_reconcile.yml, ansible/roles/postgres/templates/postgres-logical-backup-remote.sh.j2, tests/unit/test_postgres_remote_backup.py
Adds repository initialization, checksum-verified uploads, retention maintenance, locking, metrics, result reporting, and atomic managed-secret reconciliation.
Restore validation workflow
ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml, ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_action.yml, ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2, tests/unit/test_postgres_restore_validation.py
Adds scheduled and manual validation that selects Restic snapshots, checks artifacts, restores databases in an isolated PostgreSQL cluster, cleans up safely, and records metrics.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Ansible
  participant Infisical
  participant Systemd
  participant Restic
  participant PostgreSQL
  Ansible->>Infisical: resolve backup credentials
  Ansible->>Systemd: configure backup and validation jobs
  Systemd->>Restic: upload or select a snapshot
  Restic-->>Systemd: return repository or snapshot data
  Systemd->>PostgreSQL: restore databases in an isolated cluster
  PostgreSQL-->>Systemd: return validation results
Loading

Possibly related PRs

Suggested labels: codex

Poem

A rabbit checks the backup trail,
While Restic guards each tiny tale.
Snapshots hop to safety bright,
Clusters bloom, then close at night.
Metrics sing, “The restore’s right!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the PostgreSQL Restic remote backup and restore-validation features added by the pull request.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch qol/branch2

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 added the codex label Aug 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be461d1048

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml
Comment thread ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml Outdated
Comment thread docs/postgresql-logical-backups.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (11)
tests/unit/test_postgres_restore_validation.py (6)

455-458: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the redundant negative assertion.

Lines 452-454 already assert owner == "root", group == "postgres", and mode == "0710". The tuple comparison against ("root", "0700") cannot fail unless one of those three assertions already failed. It adds noise without adding coverage.

🤖 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_postgres_restore_validation.py` around lines 455 - 458,
Remove the redundant tuple inequality assertion following the existing owner,
group, and mode assertions in the work_root validation test, leaving those
direct assertions unchanged.

465-481: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert tag propagation through apply.tags for the include_role.

The test checks name, when, service fields, and timer fields. It does not check apply.tags. The include at ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml lines 151-158 must carry postgres_backup_restore_validation_setup and postgres_backup_restore_validation_run into the systemd_jobs role. If that propagation is dropped, a tagged run silently skips timer and unit creation, and no test fails.

💚 Proposed test addition
     assert include["ansible.builtin.include_role"]["name"] == "systemd_jobs"
     assert "inventory_hostname == postgres_backup_restore_validation_host" in include["when"]
+    applied_tags = include["ansible.builtin.include_role"]["apply"]["tags"]
+    assert "postgres_backup_restore_validation_setup" in applied_tags
+    assert "postgres_backup_restore_validation_run" in applied_tags
+    assert set(applied_tags) == set(include["tags"])

As per coding guidelines: "For dynamic Ansible includes, place selection tags on the outer task, propagate required tags through apply.tags, and add regression coverage when missing propagation could skip safety, validation, or lifecycle tasks."

🤖 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_postgres_restore_validation.py` around lines 465 - 481,
Update test_systemd_job_exists_only_on_designated_host_and_uses_network_online
to inspect include["ansible.builtin.include_role"]["apply"]["tags"] and assert
it contains postgres_backup_restore_validation_setup and
postgres_backup_restore_validation_run, covering tag propagation to the
systemd_jobs role.

Source: Coding guidelines


374-380: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a timeout to the validator subprocess calls.

subprocess.run has no timeout. The rendered script waits on flock, pg_ctl -w -t 60, and the fake restic. If any step hangs, the test blocks until the CI job limit and produces no useful diagnostic. A bounded timeout converts the hang into a TimeoutExpired failure at the exact test.

♻️ Proposed refactor
     return subprocess.run(
         [str(script)],
         check=False,
         capture_output=True,
         text=True,
         env=environment,
+        timeout=120,
     )
🤖 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_postgres_restore_validation.py` around lines 374 - 380,
Update the validator subprocess invocation using subprocess.run to include a
bounded timeout that covers the expected flock, pg_ctl, and fake restic
execution while preventing indefinite hangs. Keep the existing output capture,
environment, and return-code behavior unchanged so timeout failures surface at
the specific test.

639-664: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for duplicate stream tags.

The parametrised mutators cover unsafe IDs, missing and invalid backup-id, unexpected paths, and malformed tags. They do not cover the duplicate-tag rejection at lines 449-450 of ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2, which raises "duplicate stream tags". That branch is currently unexercised.

💚 Proposed test addition
         (lambda item, root: item.update({"tags": "malformed"}), "malformed tags"),
+        (
+            lambda item, root: item.update(
+                {
+                    "tags": [
+                        "postgres-logical-backup",
+                        "postgres-logical-backup",
+                        "cluster=pg-cluster",
+                        f"backup-id={BACKUP_ID_A}",
+                    ]
+                }
+            ),
+            "duplicate stream tags",
+        ),
🤖 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_postgres_restore_validation.py` around lines 639 - 664, Add a
parametrized mutator to the existing validation cases that produces duplicate
stream tags and expects the “duplicate stream tags” message. Use the same
item/root setup as the other mutators and target the duplicate-tag rejection in
the restore validation test coverage.

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

Add coverage for a non-empty postgres_backup_remote_options list.

Every test renders the template with postgres_backup_remote_options=[]. The RESTIC_OPTIONS loop at lines 31-35 of ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2 is therefore never rendered with content, and the single-quote escaping in that loop is never proven. Render one case with an option that contains a single quote and assert that the rendered script still passes bash -n and that the option reaches the fake restic command log.

As per path instructions: "Tests must prove behavior rather than merely duplicate implementation strings; static tests alone are insufficient for normalization, rendering, merge behavior, adapter portability, and runtime contracts."

🤖 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_postgres_restore_validation.py` around lines 351 - 352,
Extend the restore-validation test coverage to render a non-empty
postgres_backup_remote_options value containing a single quote. Assert the
generated script passes bash -n and that the escaped option is received by the
fake restic command log, exercising the RESTIC_OPTIONS rendering path rather
than only the empty-list case.

Source: Path instructions


939-947: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the formatting-coupled assertions with behavioral checks.

Line 947 counts the exact string "process_belongs_to_validator_pgdata \\", including the trailing backslash continuation. A reformat of the template that preserves behavior breaks this test. Lines 940-941 have the same problem for the dirname guard and the run-directory regex.

The surrounding tests at lines 963-1022 already prove the intended behavior: an exact match stops the cluster once, and every mismatch fails closed without a stop. Prefer those. If the double identity check must be enforced, assert on the identifier alone rather than on its line continuation.

As per path instructions: "Tests must prove behavior rather than merely duplicate implementation strings; static tests alone are insufficient for normalization, rendering, merge behavior, adapter portability, and runtime contracts."

🤖 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_postgres_restore_validation.py` around lines 939 - 947,
Replace the formatting-sensitive source-string assertions in the restore
validation test with behavioral coverage using the existing tests around exact
matches and mismatches, removing checks for the dirname guard and run-directory
regex text. If retaining the identity-check assertion, verify only the
identifier’s presence or behavior, not its line-continuation formatting, while
preserving the fail-closed and single-stop guarantees.

Source: Path instructions

ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2 (2)

815-818: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the restored relation count instead of discarding it.

The query result goes to /dev/null. The check proves only that the temporary cluster answers SQL after pg_restore. A dump that restores zero relations passes the validation and reports success. Capture the count and reject zero.

♻️ Proposed refactor
-  psql_value \
-    "$VALIDATION_DATABASE" \
-    'SELECT 1; SELECT count(*) FROM pg_catalog.pg_class WHERE relkind IN ('"'"'r'"'"', '"'"'p'"'"', '"'"'v'"'"', '"'"'m'"'"');' \
-    > /dev/null
+  restored_relation_count="$(
+    psql_value \
+      "$VALIDATION_DATABASE" \
+      'SELECT count(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('"'"'r'"'"', '"'"'p'"'"', '"'"'v'"'"', '"'"'m'"'"') AND n.nspname NOT IN ('"'"'pg_catalog'"'"', '"'"'information_schema'"'"');'
+  )"
+  [[ "$restored_relation_count" =~ ^[0-9]+$ ]] ||
+    fail "Restored database $source_database returned an unreadable relation count."

Do not fail on a zero count unconditionally. Some clusters hold legitimately empty databases. Consider logging the count and adding an opt-in minimum.

🤖 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
`@ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2`
around lines 815 - 818, Update the validation flow around the pg_class count
query to capture and log the restored relation count instead of redirecting it
to /dev/null. Add an opt-in minimum-count check that rejects counts below the
configured threshold, while allowing legitimately empty databases when no
minimum is configured.

561-568: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider bounding the dump scan against symlinked directories.

artifact.rglob("*.dump") walks the restored tree. In Python versions before 3.13, rglob follows directory symlinks, so a restored artifact that contains a symlink loop makes the scan run without bound. The artifact comes from the Restic repository, so this needs a corrupted or hostile repository to trigger. The scan runs as root before any other structural rejection.

An os.walk(..., followlinks=False) based scan removes the risk.

🤖 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
`@ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2`
around lines 561 - 568, Replace the artifact.rglob("*.dump") traversal with an
os.walk-based scan that uses followlinks=False, while preserving relative path
generation, regular-file validation, and exact manifest comparison in the
actual_dumps flow. Ensure symlinked directories are not traversed.
ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml (1)

136-145: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider validating the rendered script before installation.

The template writes an executable that systemd runs unattended. Add validate: bash -n %s so a broken render fails at deploy time instead of at the next timer run.

♻️ Proposed refactor
     dest: "{{ postgres_backup_restore_validation_script_path }}"
     owner: root
     group: root
     mode: "0755"
+    validate: bash -n %s
🤖 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 `@ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml`
around lines 136 - 145, Update the “Backup restore validation | Install
host-local runner” template task to validate the rendered script with Bash
syntax checking before installation by adding the appropriate validate setting
using the temporary-file placeholder. Keep the existing template source,
destination, ownership, permissions, and conditions unchanged.
tests/unit/test_postgres_remote_backup.py (1)

518-532: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prove reserved-name rejection by behavior, not by substring counts.

The assertion counts implementation strings. A prefix match such as - ENV also matches a longer name, so the test can pass without the guard existing. The parametrized test at lines 535-559 covers only three of the ten names.

Extend the parametrized rejection test to every reserved name and keep the static check only as a secondary signal.

As per coding guidelines: "Tests must prove behavior rather than merely duplicate implementation strings; static tests alone are insufficient for normalization, rendering, merge behavior, adapter portability, and runtime contracts."

🤖 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_postgres_remote_backup.py` around lines 518 - 532, Extend the
parametrized reserved-name rejection test near the existing three-name coverage
to include all ten names listed in
test_reserved_backend_environment_names_cover_shell_startup_and_loader_controls.
Keep that behavioral test as the primary verification for every name, and retain
the source-count assertion only as a secondary static check, tightening its
matching if needed to avoid prefix collisions.

Source: Coding guidelines

ansible/roles/postgres/tasks/sub_tasks/backup_remote_action.yml (1)

21-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

changed_when always reports changed for the maintenance action.

Line 35 marks every maintenance run as changed. A maintenance run that forgets no snapshot is a no-op. Idempotent reporting needs a distinct runner signal, for example POSTGRES_BACKUP_REMOTE_RESULT=MAINTENANCE_COMPLETE=<removed-count>, and a matching pattern here.

As per coding guidelines: "Use ... idempotent tasks, and accurate changed_when and failed_when behavior."

🤖 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 `@ansible/roles/postgres/tasks/sub_tasks/backup_remote_action.yml` around lines
21 - 35, Update changed_when for the maintenance branch so it no longer
unconditionally reports changes; match a runner result signal such as
POSTGRES_BACKUP_REMOTE_RESULT=MAINTENANCE_COMPLETE=<removed-count>, requiring a
positive removed count. Preserve the existing init and upload conditions and
align the maintenance pattern with the runner’s emitted output.

Source: Coding guidelines

🤖 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 `@ansible/roles/postgres/tasks/main.yml`:
- Around line 143-155: Convert both tags and apply.tags from scalar values to
one-item YAML sequences in the eight PostgreSQL backup tasks at
ansible/roles/postgres/tasks/main.yml lines 143-155, 157-168, 170-182, 184-192,
194-203, 205-215, 231-241, and 243-253. Regenerate
ansible/roles/postgres/README.md using the documentation tooling, including the
generated section at lines 136-145, and inspect the diff for unrelated changes;
do not hand-edit the README.

In `@ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml`:
- Around line 147-158: Update the “Backup restore validation | Manage systemd
job” task to clean up validation service, timer, and runner artifacts on hosts
that are no longer equal to postgres_backup_restore_validation_host, while
preserving systemd_jobs role management on the currently designated host. Use
the existing systemd job symbols and ensure cleanup runs when the designated
host changes.

In `@ansible/roles/postgres/templates/postgres-logical-backup-remote.sh.j2`:
- Around line 182-224: Enforce the supported Restic version floor in
require_initialized_repository and initialize_repository, or explicitly
distinguish exit code 1’s missing-repository and wrong-password cases before
rejecting initialization; preserve correct behavior for init, upload, and
maintenance. In tests/unit/test_postgres_remote_backup.py lines 109-122, add
fake-Restic modes returning 1 for both cases and assert behavior with the
supported version. In docs/postgresql-logical-backups.md lines 180-187, document
the minimum Restic version alongside exit codes 10 and 12.

In
`@ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2`:
- Around line 740-747: Update the systemd job definition for the
restore-validation runner in backup_restore_validation_setup.yml to set an
explicit TimeoutStartSec that safely covers the complete restore and checksum
validation. Reuse the existing systemd_jobs template timeout mechanism if
available; otherwise add support for passing this setting through the template,
ensuring it applies to the full Type=oneshot ExecStart.
- Around line 756-762: Update the initdb invocation in the validation-cluster
setup to explicitly set the cluster encoding and locale to match production,
rather than inheriting systemd’s environment. Expose these values as role
variables so deployments with different production settings can override them,
and use a locale such as C.UTF-8 only if it is confirmed available on the target
image.
- Around line 467-476: The snapshot timestamp parsing in the validation flow
must accept Restic timestamps with seven to nine fractional-second digits.
Normalize the string in the block using snapshot_time before
datetime.datetime.fromisoformat, truncating only the fractional portion to at
most six digits while preserving timezone information, and add a fixture
covering a nine-digit fractional timestamp.

In `@docs/postgresql-logical-backups.md`:
- Around line 12-16: Update the remote-layer scope statement to remove only the
claim that automated disposable restore tests are deferred, while preserving the
other deferred capabilities and the existing implemented restore-test
documentation.

In `@tests/unit/test_postgres_remote_backup.py`:
- Around line 109-122: Add a fake-restic compatibility mode in the command
dispatch that returns exit code 1 for both missing-repository and wrong-password
cases, then extend the relevant remote-backup runner tests to use this mode and
assert those conditions are classified with the same supported behavior as the
current exit-code-specific cases.

In `@tests/unit/test_postgres_restore_validation.py`:
- Around line 890-921: Update render_validator and
test_overlap_skip_does_not_overwrite_health_metrics so the fake pg_restore
performs a real blocking delay when FAKE_PG_RESTORE_SLEEP is set, using an
absolute system sleep or a distinct unshadowed delay command. Preserve the
runner’s no-sleep behavior if required, while ensuring the first validator
remains active long enough for the second to assert SKIPPED_OVERLAP reliably.

---

Nitpick comments:
In `@ansible/roles/postgres/tasks/sub_tasks/backup_remote_action.yml`:
- Around line 21-35: Update changed_when for the maintenance branch so it no
longer unconditionally reports changes; match a runner result signal such as
POSTGRES_BACKUP_REMOTE_RESULT=MAINTENANCE_COMPLETE=<removed-count>, requiring a
positive removed count. Preserve the existing init and upload conditions and
align the maintenance pattern with the runner’s emitted output.

In `@ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml`:
- Around line 136-145: Update the “Backup restore validation | Install
host-local runner” template task to validate the rendered script with Bash
syntax checking before installation by adding the appropriate validate setting
using the temporary-file placeholder. Keep the existing template source,
destination, ownership, permissions, and conditions unchanged.

In
`@ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2`:
- Around line 815-818: Update the validation flow around the pg_class count
query to capture and log the restored relation count instead of redirecting it
to /dev/null. Add an opt-in minimum-count check that rejects counts below the
configured threshold, while allowing legitimately empty databases when no
minimum is configured.
- Around line 561-568: Replace the artifact.rglob("*.dump") traversal with an
os.walk-based scan that uses followlinks=False, while preserving relative path
generation, regular-file validation, and exact manifest comparison in the
actual_dumps flow. Ensure symlinked directories are not traversed.

In `@tests/unit/test_postgres_remote_backup.py`:
- Around line 518-532: Extend the parametrized reserved-name rejection test near
the existing three-name coverage to include all ten names listed in
test_reserved_backend_environment_names_cover_shell_startup_and_loader_controls.
Keep that behavioral test as the primary verification for every name, and retain
the source-count assertion only as a secondary static check, tightening its
matching if needed to avoid prefix collisions.

In `@tests/unit/test_postgres_restore_validation.py`:
- Around line 455-458: Remove the redundant tuple inequality assertion following
the existing owner, group, and mode assertions in the work_root validation test,
leaving those direct assertions unchanged.
- Around line 465-481: Update
test_systemd_job_exists_only_on_designated_host_and_uses_network_online to
inspect include["ansible.builtin.include_role"]["apply"]["tags"] and assert it
contains postgres_backup_restore_validation_setup and
postgres_backup_restore_validation_run, covering tag propagation to the
systemd_jobs role.
- Around line 374-380: Update the validator subprocess invocation using
subprocess.run to include a bounded timeout that covers the expected flock,
pg_ctl, and fake restic execution while preventing indefinite hangs. Keep the
existing output capture, environment, and return-code behavior unchanged so
timeout failures surface at the specific test.
- Around line 639-664: Add a parametrized mutator to the existing validation
cases that produces duplicate stream tags and expects the “duplicate stream
tags” message. Use the same item/root setup as the other mutators and target the
duplicate-tag rejection in the restore validation test coverage.
- Around line 351-352: Extend the restore-validation test coverage to render a
non-empty postgres_backup_remote_options value containing a single quote. Assert
the generated script passes bash -n and that the escaped option is received by
the fake restic command log, exercising the RESTIC_OPTIONS rendering path rather
than only the empty-list case.
- Around line 939-947: Replace the formatting-sensitive source-string assertions
in the restore validation test with behavioral coverage using the existing tests
around exact matches and mismatches, removing checks for the dirname guard and
run-directory regex text. If retaining the identity-check assertion, verify only
the identifier’s presence or behavior, not its line-continuation formatting,
while preserving the fail-closed and single-stop guarantees.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e99aee6-327d-46aa-ac71-ad2f84506e52

📥 Commits

Reviewing files that changed from the base of the PR and between ea89a98 and 2aaf0ed.

📒 Files selected for processing (18)
  • ansible/playbook.yml
  • ansible/roles/postgres/README.md
  • ansible/roles/postgres/defaults/main.yml
  • ansible/roles/postgres/tasks/main.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_remote_action.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_remote_secret_reconcile.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_remote_setup.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_action.yml
  • ansible/roles/postgres/tasks/sub_tasks/backup_restore_validation_setup.yml
  • ansible/roles/postgres/templates/postgres-logical-backup-remote.env.j2
  • ansible/roles/postgres/templates/postgres-logical-backup-remote.sh.j2
  • ansible/roles/postgres/templates/postgres-logical-backup-restore-validate.sh.j2
  • ansible/roles/ubuntu/templates/skynet.j2
  • docs/cheat_sheets/skynet.md
  • docs/postgresql-logical-backups.md
  • tests/unit/test_postgres_remote_backup.py
  • tests/unit/test_postgres_restore_validation.py
  • tests/unit/test_service_common_static.py

Comment thread ansible/roles/postgres/tasks/main.yml Outdated
Comment thread docs/postgresql-logical-backups.md Outdated
Comment thread tests/unit/test_postgres_remote_backup.py
Comment thread tests/unit/test_postgres_restore_validation.py
@Lebowski89 Lebowski89 changed the title Qol/branch2 feat(postgres): add Restic remote backups and restore validation Aug 12, 2026
@Lebowski89
Lebowski89 merged commit 657b424 into main Aug 12, 2026
9 checks passed
@Lebowski89
Lebowski89 deleted the qol/branch2 branch August 12, 2026 02:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant