refactor: read facts through ansible_facts and stop injecting them #5

Merged
claudiomaradonna merged 4 commits from refactor/ansible-facts-access into main 2026-09-25 14:35:13 +02:00

Why

ansible-core deprecates INJECT_FACTS_AS_VARS and stops injecting top-level
ansible_* fact variables in 2.24. Every role still reading them printed a
deprecation warning. For firewall-apply the removal would have been silent and
harmful: with injection off, peer derivation filters out every peer, and
from_group rules for swarm, gluster and ai-stack get dropped.

What changes

  • Roles (zram, docker-engine, fail2ban-basic, hardening-basic, os-updates):
    os_family, distribution, distribution_release and service_mgr are
    read as ansible_facts['…'].
  • firewall-apply: peers are derived from ansible_facts.default_ipv4.address.
    The select stays on the leaf key, not the parent (the d0b5639 regression).
  • ai-stack, warden: the default bind and listen addresses read
    ansible_facts['default_ipv4']['address']. The value, the empty fallback
    and the non-empty asserts are unchanged.
  • peer_derivation gate: the fixture used to set ansible_default_ipv4 as
    an inventory variable, and that never reaches hostvars[h].ansible_facts.
    That is why the 2026-08-27 attempt failed. The new prime.yml writes the
    facts into a temporary fact cache with set_fact (cacheable and
    delegate_facts). The derivation then reads them in a separate run, with
    injection on and with injection off.
  • ansible.cfg: inject_facts_as_vars = False, so any new use fails as
    undefined instead of printing a warning.

Blast radius

  • Firewall: these changes can only close rules, never open them. If a
    peer's address can't be found, its rule is dropped, not opened. The
    worst case is lost cluster traffic for swarm, gluster or ai-stack. SSH does
    not depend on peer addresses.
  • Bind addresses: if the ai-stack or warden bind address comes out empty,
    the role fails by name.
  • Expected production diff: none. Both forms read the same gathered or
    cached facts, and no tenant inventory/ or host_vars/ file sets or reads
    injected facts.

Verification

  • make check: passes with inject_facts_as_vars = False.
  • Two deliberate breakages, both caught by the peer derivation gate:
  • with the old ansible_default_ipv4 expression, the injection-off run fails;
  • selecting on the parent key fails.
  • Not run yet: make test (molecule) and make verify. They need podman.
  • Before a live run, with approval: --check --diff --tags firewall on each
    tenant, before and after. The rendered nftables table should be
    byte-identical.
## Why ansible-core deprecates `INJECT_FACTS_AS_VARS` and stops injecting top-level `ansible_*` fact variables in 2.24. Every role still reading them printed a deprecation warning. For firewall-apply the removal would have been silent and harmful: with injection off, peer derivation filters out every peer, and `from_group` rules for swarm, gluster and ai-stack get dropped. ## What changes - **Roles** (zram, docker-engine, fail2ban-basic, hardening-basic, os-updates): `os_family`, `distribution`, `distribution_release` and `service_mgr` are read as `ansible_facts['…']`. - **firewall-apply**: peers are derived from `ansible_facts.default_ipv4.address`. The select stays on the leaf key, not the parent (the d0b5639 regression). - **ai-stack, warden**: the default bind and listen addresses read `ansible_facts['default_ipv4']['address']`. The value, the empty fallback and the non-empty asserts are unchanged. - **peer_derivation gate**: the fixture used to set `ansible_default_ipv4` as an inventory variable, and that never reaches `hostvars[h].ansible_facts`. That is why the 2026-08-27 attempt failed. The new `prime.yml` writes the facts into a temporary fact cache with `set_fact` (`cacheable` and `delegate_facts`). The derivation then reads them in a separate run, with injection on and with injection off. - **ansible.cfg**: `inject_facts_as_vars = False`, so any new use fails as undefined instead of printing a warning. ## Blast radius - **Firewall:** these changes can only close rules, never open them. If a peer's address can't be found, its rule is dropped, not opened. The worst case is lost cluster traffic for swarm, gluster or ai-stack. SSH does not depend on peer addresses. - **Bind addresses:** if the ai-stack or warden bind address comes out empty, the role fails by name. - **Expected production diff: none.** Both forms read the same gathered or cached facts, and no tenant `inventory/` or `host_vars/` file sets or reads injected facts. ## Verification - `make check`: passes with `inject_facts_as_vars = False`. - Two deliberate breakages, both caught by the peer derivation gate: - with the old `ansible_default_ipv4` expression, the injection-off run fails; - selecting on the parent key fails. - Not run yet: `make test` (molecule) and `make verify`. They need podman. - Before a live run, with approval: `--check --diff --tags firewall` on each tenant, before and after. The rendered nftables table should be byte-identical.
Reference os_family, distribution, distribution_release and
service_mgr through ansible_facts['...'] instead of the injected
top-level ansible_* variables. This removes the
INJECT_FACTS_AS_VARS deprecation warning for these facts and keeps
the roles working once injection is turned off.

ansible_default_ipv4 is left for a follow-up: the peer derivation
fixture can only hand-set it as an inventory variable (see
tests/fixtures/peer_derivation/inventory.yml), which does not
populate hostvars[h].ansible_facts.

The hardening-basic molecule comments and the include_tasks example
in docs/ARCHITECTURE.md are updated to match.
Peer derivation read the injected ansible_default_ipv4, which
ansible-core 2.24 stops injecting. With injection off every peer
is filtered out, and every restricted rule (swarm, gluster,
ai-stack) is dropped. Read the address through
ansible_facts.default_ipv4 instead.

The fixture could only hand-set that fact as an inventory
variable, which never reaches hostvars[h].ansible_facts. prime.yml
now caches the facts with set_fact cacheable and delegate_facts
into a throwaway cache, and the gate derives in a separate run
with fact injection both on and off.

Verified: the gate passes both ways, the old expression fails
with injection off, and the parent-key regression (d0b5639) is
still caught.
ai_stack_bind_address and warden_listen_address defaulted to the
injected ansible_default_ipv4, which ansible-core 2.24 no longer
provides. Read the same value through
ansible_facts['default_ipv4']['address'] so the defaults survive
injection being turned off; the empty fallback and the roles'
non-empty asserts are unchanged.
Adopt the ansible-core 2.24 default now: with
inject_facts_as_vars = False, any new use of an injected ansible_*
fact fails as undefined instead of only printing a deprecation
warning.

make check (VENV=.venv-jail) passes with it. make test (molecule)
was not run: no podman in this environment.
claudiomaradonna deleted branch refactor/ansible-facts-access 2026-09-25 14:35:17 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
claudiomaradonna/castrum!5
No description provided.