kanidm: the provision task reports changed on every converge (unconditional OAuth2 tile-icon upload) #317

Stängd
öppnade 2026-08-01 18:03:54 +00:00 av supernaut · 0 kommentarer
Ägare

The kanidm role reports changed=1 on every converge, forever, on an otherwise fully converged
host. Found while applying #307 to production on 2026-08-01.

Symptom

Two consecutive ansible-playbook site.yml --limit gitborg-prod --tags kanidm runs, no changes between
them:

run 1:  ok=35  changed=1   →  kanidm : Provision entitlement groups (kanidm-provision — idempotent)
run 2:  ok=35  changed=1   →  kanidm : Provision entitlement groups (kanidm-provision — idempotent)

The task carries no_log: true, so the reason is not visible in ordinary output.

Cause

Re-running the identical kanidm-provision command and filtering its log to the mutation verbs the
changed_when matches on returns exactly one line, on every run:

    Updating /v1/oauth2/forgejo/_image

That is the OAuth2 tile icon (logo-square.svg) being re-uploaded to the forgejo client. It appears to
be the one write the tool does not guard behind a comparison — update_entity_attrs() wraps each attribute
mutation in if current_values != values, but the image upload is a separate path with nothing to compare
an already-uploaded binary against. Stated as inference from observed behaviour; it was not confirmed
against upstream source.

Exactly one client declares image_file (forgejo), which matches the single line observed — so this is
one unconditional PUT per converge, not a per-client multiplier.

Nothing else is mutated — this is noise, not damage

The filtered log contained that line and nothing else. No group, person, or membership write occurred.
Worth stating explicitly given this repo's history with membership pruning: the tile icon is the entire
mutation, and --no-auto-remove is unaffected.

Why it is still worth fixing

changed on a converged host is the signal for real drift. Pinning it permanently true on this role means
every future apply shows a change that must be manually dismissed — which is exactly how a genuine
identity-state change gets waved through. It also breaks the "account for every changed task" step of the
apply routine for anyone converging kanidm.

The existing changed_when comment already identifies this failure mode and guards against one instance of
it: Appending is deliberately excluded because "Matching it would pin changed:true permanently." The
image Updating pins it permanently through a different verb. The history in that same comment records the
predicate having been wrong twice before in the other direction, so it is worth changing carefully rather
than quickly.

Not from #307

Introduced with #287 (tile icon). #307 changed zero icon-related lines — it only surfaced this, because
applying it meant running the role on a converged host and accounting for the result.

Suggested

Two options, neither obviously better:

  1. Narrow changed_when so an Updating whose target ends in /_image does not count, mirroring how
    Appending is already excluded. Cheap and local, but adds a second special case to a predicate that has
    already been wrong twice, and it would also mask a genuine icon change.
  2. Make the upload conditional — only pass imageFile in the rendered state when the SVG's checksum
    differs from what is live. Fixes the cause rather than the reporting, but there is no obvious read-back
    endpoint for the current image, so this may not be expressible.

Option 1 with a comment recording why is probably right, but the trade-off is worth a moment's thought
rather than a reflex.

Refs #285, #287, #307.

The `kanidm` role reports `changed=1` on **every** converge, forever, on an otherwise fully converged host. Found while applying #307 to production on 2026-08-01. ## Symptom Two consecutive `ansible-playbook site.yml --limit gitborg-prod --tags kanidm` runs, no changes between them: ```text run 1: ok=35 changed=1 → kanidm : Provision entitlement groups (kanidm-provision — idempotent) run 2: ok=35 changed=1 → kanidm : Provision entitlement groups (kanidm-provision — idempotent) ``` The task carries `no_log: true`, so the reason is not visible in ordinary output. ## Cause Re-running the identical `kanidm-provision` command and filtering its log to the mutation verbs the `changed_when` matches on returns exactly one line, on every run: ```text Updating /v1/oauth2/forgejo/_image ``` That is the OAuth2 tile icon (`logo-square.svg`) being re-uploaded to the `forgejo` client. It appears to be the one write the tool does not guard behind a comparison — `update_entity_attrs()` wraps each attribute mutation in `if current_values != values`, but the image upload is a separate path with nothing to compare an already-uploaded binary against. Stated as inference from observed behaviour; it was not confirmed against upstream source. Exactly one client declares `image_file` (`forgejo`), which matches the single line observed — so this is one unconditional PUT per converge, not a per-client multiplier. ## Nothing else is mutated — this is noise, not damage The filtered log contained that line and nothing else. **No group, person, or membership write occurred.** Worth stating explicitly given this repo's history with membership pruning: the tile icon is the entire mutation, and `--no-auto-remove` is unaffected. ## Why it is still worth fixing `changed` on a converged host is the signal for real drift. Pinning it permanently true on this role means every future apply shows a change that must be manually dismissed — which is exactly how a genuine identity-state change gets waved through. It also breaks the "account for every changed task" step of the apply routine for anyone converging `kanidm`. The existing `changed_when` comment already identifies this failure mode and guards against one instance of it: `Appending` is deliberately excluded because *"Matching it would pin changed:true permanently."* The image `Updating` pins it permanently through a different verb. The history in that same comment records the predicate having been wrong twice before in the other direction, so it is worth changing carefully rather than quickly. ## Not from #307 Introduced with #287 (tile icon). #307 changed zero icon-related lines — it only surfaced this, because applying it meant running the role on a converged host and accounting for the result. ## Suggested Two options, neither obviously better: 1. **Narrow `changed_when`** so an `Updating` whose target ends in `/_image` does not count, mirroring how `Appending` is already excluded. Cheap and local, but adds a second special case to a predicate that has already been wrong twice, and it would also mask a genuine icon change. 2. **Make the upload conditional** — only pass `imageFile` in the rendered state when the SVG's checksum differs from what is live. Fixes the cause rather than the reporting, but there is no obvious read-back endpoint for the current image, so this may not be expressible. Option 1 with a comment recording why is probably right, but the trade-off is worth a moment's thought rather than a reflex. Refs #285, #287, #307.
Logga in för att delta i denna konversation.
Ingen milstolpe
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra#317
Ingen beskrivning angiven.