Skip to content

fix(ansible): make --check succeed on a fresh host - #6

Merged
thiras merged 3 commits into
mainfrom
fix/check-mode-fresh-host-guards
Jun 6, 2026
Merged

thiras merged 3 commits into
mainfrom
fix/check-mode-fresh-host-guards

Conversation

@thiras

@thiras thiras commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Problem

make check / make check-anvil (ansible-playbook --check) crashed on a not-yet-provisioned host. In check mode the package-install and user-creation tasks are no-ops, so every later task that depends on that state hard-failed in turn:

  • authorized_key — "Either user must exist or you must provide full path to key file in check mode" (needs the admin user's home dir)
  • systemd enable/start of packaged units (nftables/fail2ban/chrony/anvil/caddy) — "Could not find the requested service"
  • become_user: anvil for the Foundry install — "Failed to change ownership of the temporary files…"
  • the cast mnemonic generation, the caddy basic-auth registry slurp, and the flush_handlers restarts

Approach

The Makefile contract is that check "mutates nothing", so rather than force installs in check mode (check_mode: false), this probes real on-host state with read-only, check-mode-safe modules (getent, service_facts, stat) and guards each dependent task/handler:

when: not ansible_check_mode or <prerequisite-exists-probe>
  • Real deploys always run — not ansible_check_mode short-circuits to true.
  • Check runs on an already-provisioned host still preview drift (the probe finds the prerequisite).
  • Only a fresh host in check mode skips the install-dependent steps.

Subtleties (documented inline)

  • getent with fail_key: false stores a missing key as {key: None}, so existence is tested via .get(key) truthiness, not key in dict.
  • Service-existence guards use ansible_facts.services | default({}) — service_facts is gathered once in baseline and reused across the anvil/caddy roles and all handlers.

Verification

  • make check-anvil completes failed=0 on a fresh host (install-dependent tasks skipping).
  • site.yml's baseline + DevSec hardening now pass check; it stops only at the by-design decdn_node_version assert (operator config, not a bug).
  • make deploy is unaffected (every guard short-circuits true in real runs).
  • ansible-lint (production profile) + yamllint clean.

Reviewed

Ran the multi-agent PR review (code / comments / silent-failure / tests). Applied: uniform | default({}) on the three baseline service guards, and a comment-accuracy fix in caddy/tasks/main.yml. One follow-up noted but not included: there is no automated check-mode test — a future molecule scenario that converges --check against a fresh container would guard the pattern against regressions.

🤖 Generated with Claude Code

`make check` / `make check-anvil` (ansible-playbook --check) crashed on a
not-yet-provisioned host. In check mode the package-install and user-creation
tasks are no-ops, so any later task depending on that state — authorized_key
(needs the admin user's home), become_user, systemd enable/start of packaged
units (nftables/fail2ban/chrony/anvil/caddy), the Foundry/cast install, the
mnemonic generation, the caddy basic-auth registry slurp, and the
flush_handlers restarts — hard-failed.

The Makefile contract is that check "mutates nothing", so rather than force
installs in check mode, probe real on-host state with read-only,
check-mode-safe modules (getent, service_facts, stat) and guard each
dependent task/handler with `when: not ansible_check_mode or <prerequisite>`.
Real deploys always run (the `not ansible_check_mode` short-circuits true);
check runs on an already-provisioned host still preview drift; only a fresh
host in check mode skips the install-dependent steps.

Notes:
- getent with fail_key:false stores a missing key as {key: None}, so existence
  is tested via .get(key) truthiness, not `key in dict`.
- service-existence guards use `ansible_facts.services | default({})`
  (service_facts is gathered once in baseline and reused across roles).

Verified: `make check-anvil` completes failed=0 on a fresh host; site.yml's
baseline + hardening now pass check (it stops only at the by-design
decdn_node_version assert). ansible-lint (production profile) + yamllint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 6, 2026 22:34

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces check-mode (--check) safety guards across the anvil, baseline, and caddy Ansible roles to prevent dry-run failures on unprovisioned hosts by probing for the existence of users, files, and services. The review feedback recommends skipping the Anvil URI reachability probe entirely during check mode to avoid false failures when the service is stopped, and suggests explicitly gathering service_facts within the anvil and caddy roles to ensure they remain robust and self-contained when executed independently.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread ansible/roles/anvil/tasks/main.yml Outdated
Comment thread ansible/roles/anvil/tasks/main.yml
Comment thread ansible/roles/caddy/tasks/main.yml

Copilot AI 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.

Pull request overview

This PR aims to make make check / make check-anvil (ansible-playbook --check) succeed on a fresh (not-yet-provisioned) host by probing real host state with check-mode-safe modules and guarding tasks/handlers that would otherwise hard-fail when prerequisite users, files, or systemd units don’t exist yet.

Changes:

  • Add service existence probing (service_facts) and gate systemd tasks/handlers on unit presence during check mode.
  • Add user/file existence probing (getent, stat) and gate dependent tasks to avoid check-mode failures on fresh hosts.
  • Apply the same guarding pattern across baseline, anvil, and caddy roles (including handlers and config validation).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
ansible/roles/caddy/tasks/main.yml Adds file existence probe for the TSV registry and check-mode guards for slurp/render, plus service-based guards for validation/systemd tasks.
ansible/roles/caddy/handlers/main.yml Guards the restart handler to avoid failing in check mode when the unit doesn’t exist.
ansible/roles/baseline/tasks/main.yml Gathers service_facts and guards systemd + authorized_key operations in check mode using host-state probes.
ansible/roles/baseline/handlers/main.yml Guards nftables/fail2ban handlers in check mode based on probed unit existence.
ansible/roles/anvil/tasks/main.yml Adds user probe and guards Foundry install/mnemonic/systemd/RPC probe steps in check mode using host-state probes.
ansible/roles/anvil/handlers/main.yml Guards the restart handler to avoid failing in check mode when the unit doesn’t exist.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ansible/roles/anvil/tasks/main.yml
Comment thread ansible/roles/anvil/tasks/main.yml
Comment thread ansible/roles/caddy/tasks/main.yml
thiras and others added 2 commits June 7, 2026 01:43
…ervice_facts

- anvil RPC liveness probe: skip entirely in check mode (`when: not
  ansible_check_mode`). --check never (re)starts anvil, so guarding on unit
  existence would falsely fail against a stopped-but-provisioned host; the probe
  is changed_when:false and previews no drift.
- anvil + caddy: gather service_facts when `ansible_facts.services is not
  defined`, so the roles stay correct when run standalone (--tags) without
  baseline gathering them first.

Verified: `make check-anvil` still failed=0 on a fresh host; lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Mint block was gated only on `caddy_user_check.rc != 0`. Under --check the
existence-check command auto-skips, leaving caddy_user_check without `rc`, so the
guard relied on Ansible's lenient evaluation of an undefined attribute (version-
dependent). Gate the block on `not ansible_check_mode` first — minting needs the
caddy binary (hash-password, itself check-skipped), writes the TSV, and reveals a
password, none of which a dry run should do — and default the rc so it can never
error. Gating the mutating block (vs the whole include) keeps the username-
validation assert running in check mode.

Verified: `make check-anvil` failed=0 on a fresh host, Mint block skips cleanly;
lint clean. Real-mode minting path is covered by molecule converge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thiras
thiras merged commit 0a3f101 into main Jun 6, 2026
7 checks passed
@thiras
thiras deleted the fix/check-mode-fresh-host-guards branch June 6, 2026 22:52
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