compose_check: Digest-Vergleich bei Multi-Arch-Images falsch — dauerhafter False-Positive bei :latest #91

Open
opened 2026-06-09 21:43:18 +00:00 by chinux · 1 comment
Owner

Problem (live reproduziert)

Der Compose-Update-Check meldet bei Multi-Arch-Images (z. B. jc21/nginx-proxy-manager:latest) dauerhaft "Update verfuegbar" — auch unmittelbar nach erfolgreichem Update + Recheck.

Ursache

compose_check vergleicht zwei verschiedene Digest-Ebenen (VM-Guest-Agent/src/collect/mod.rs:137-138):

cur=$(docker image inspect "$img" --format '{{index .RepoDigests 0}}')        # lokal: per-Arch-Manifest-Digest
rem=$(docker buildx imagetools inspect "$img" --format '{{.Manifest.Digest}}') # remote: Digest der Manifest-LISTE (Index)
update_available = (cur != rem)

Bei Multi-Arch-Images ist rem der Index-Digest (Sammeldigest ueber alle Architekturen), cur je nach Image-Store der per-Arch-Digest. Die sind strukturell verschieden → immer ungleich → permanenter False-Positive. Single-Arch-Images sind nicht betroffen (deshalb faellt es nur bei manchen Images auf).

Fix

Gleiches mit gleichem vergleichen — empfohlen Kombination:

  1. Per-Arch sauber aufloesen: docker buildx imagetools inspect "$img" --raw liefert das Index-JSON; daraus den manifests[]-Eintrag picken, dessen platform zur Host-Plattform passt (docker version --format '{{.Server.Os}}/{{.Server.Arch}}'), dessen digest als rem_arch nehmen → gegen lokalen RepoDigests[0] vergleichen.
  2. Sicherheitsnetz (deckt classic- UND containerd-Store ab): zusaetzlich den Index-Digest (.Manifest.Digest) als rem_index behalten und "aktuell" werten, wenn der lokale Digest einem von beiden entspricht:
    update_available = (cur != rem_arch && cur != rem_index)
  3. Wenn --raw/jq nicht verfuegbar: bisheriges Verhalten als Fallback, aber Ergebnis als unknown statt true werten (kein False-Positive).

Hinweis: im Gast ist nicht zwingend jq vorhanden — das Index-JSON ggf. im Rust-Code parsen (Agent bekommt --raw-Output zurueck) statt in der Shell.

Akzeptanz

  • Multi-Arch-:latest (NPM-Testfall): nach Update + Recheck → "aktuell".
  • Single-Arch-Images weiterhin korrekt erkannt.
  • Echte Updates (neuer Push auf den Tag) werden weiterhin gemeldet.
  • Bei nicht ermittelbarem Remote-Digest: unknown, nie False-Positive.

Bezug

#42 (Digest-Check eingefuehrt), #43 (Relay ueber VM-Guest-Agent), #88-#90 (Daily-Check/Live-Log nutzen dieselbe Logik → profitieren direkt).

Branch

fix/compose-check-multiarch-digest

## Problem (live reproduziert) Der Compose-Update-Check meldet bei Multi-Arch-Images (z. B. `jc21/nginx-proxy-manager:latest`) **dauerhaft "Update verfuegbar"** — auch unmittelbar nach erfolgreichem Update + Recheck. ## Ursache `compose_check` vergleicht **zwei verschiedene Digest-Ebenen** (`VM-Guest-Agent/src/collect/mod.rs:137-138`): ```sh cur=$(docker image inspect "$img" --format '{{index .RepoDigests 0}}') # lokal: per-Arch-Manifest-Digest rem=$(docker buildx imagetools inspect "$img" --format '{{.Manifest.Digest}}') # remote: Digest der Manifest-LISTE (Index) update_available = (cur != rem) ``` Bei Multi-Arch-Images ist `rem` der **Index-Digest** (Sammeldigest ueber alle Architekturen), `cur` je nach Image-Store der **per-Arch-Digest**. Die sind strukturell verschieden → **immer ungleich** → permanenter False-Positive. Single-Arch-Images sind nicht betroffen (deshalb faellt es nur bei manchen Images auf). ## Fix Gleiches mit gleichem vergleichen — empfohlen Kombination: 1. **Per-Arch sauber aufloesen:** `docker buildx imagetools inspect "$img" --raw` liefert das Index-JSON; daraus den `manifests[]`-Eintrag picken, dessen `platform` zur Host-Plattform passt (`docker version --format '{{.Server.Os}}/{{.Server.Arch}}'`), dessen `digest` als `rem_arch` nehmen → gegen lokalen `RepoDigests[0]` vergleichen. 2. **Sicherheitsnetz (deckt classic- UND containerd-Store ab):** zusaetzlich den Index-Digest (`.Manifest.Digest`) als `rem_index` behalten und "aktuell" werten, wenn der lokale Digest **einem von beiden** entspricht: `update_available = (cur != rem_arch && cur != rem_index)` 3. Wenn `--raw`/jq nicht verfuegbar: bisheriges Verhalten als Fallback, aber Ergebnis als `unknown` statt `true` werten (kein False-Positive). Hinweis: im Gast ist nicht zwingend `jq` vorhanden — das Index-JSON ggf. im Rust-Code parsen (Agent bekommt `--raw`-Output zurueck) statt in der Shell. ## Akzeptanz - Multi-Arch-`:latest` (NPM-Testfall): nach Update + Recheck → "aktuell". - Single-Arch-Images weiterhin korrekt erkannt. - Echte Updates (neuer Push auf den Tag) werden weiterhin gemeldet. - Bei nicht ermittelbarem Remote-Digest: `unknown`, nie False-Positive. ## Bezug #42 (Digest-Check eingefuehrt), #43 (Relay ueber VM-Guest-Agent), #88-#90 (Daily-Check/Live-Log nutzen dieselbe Logik → profitieren direkt). ## Branch `fix/compose-check-multiarch-digest`
Author
Owner

Konkreter Fix (Weg B — arch-Digest gegen arch-Digest)

Bestaetigt @ aktuell: compose_check (VM-Guest-Agent/src/collect/mod.rs:362-363) vergleicht

  • cur = RepoDigests[0]arch-spezifischer Manifest-Digest der lokal gezogenen Plattform
  • rem = imagetools inspect --format {{.Manifest.Digest}} → bei Multi-Arch-:latest der Index-Digest (Manifest-Liste ueber ALLE Archs)

→ die beiden sind per Definition nie gleich → cur != rem (:380) immer true → dauerhafter False-Positive. Die umgebende Logik (leere Digests → unknown/Null) ist korrekt; nur der Vergleich vergleicht Index gegen Arch.

Fix

Remote ebenfalls den arch-spezifischen Digest fuer die lokale Plattform aufloesen, statt des Index:

  1. Lokale Plattform bestimmen: docker version -f '{{.Server.Os}}/{{.Server.Arch}}' (bzw. os/arch/variant; arm braucht ggf. v7/v8).
  2. Remote die Manifest-Liste roh holen: docker buildx imagetools inspect "$img" --raw (JSON: manifests[] mit platform.{os,architecture,variant} + digest).
  3. Aus der Liste den Eintrag mit passender Plattform herauspicken → dessen digest = rem.
  4. Vergleich cur != rem wie bisher.

Sicherheitsnetz (kein neuer False-Positive)

  • Single-Arch-Image (kein manifests[], sondern direkt ein Manifest): --raw liefert keinen Index → dann rem = der direkte Manifest-Digest ({{.Manifest.Digest}} wie bisher ist hier korrekt). Erkennen am mediaType (image.index vs image.manifest) bzw. am Fehlen von manifests[].
  • Plattform nicht in der Liste / Parsing/Tool schlaegt fehl / buildx fehlt: NICHT update_available=true setzen, sondern unknown (Null) — lieber kein Signal als ein falsches.
  • Der bestehende Pull---dry-run-Fallback bleibt fuer "compose/buildx fehlt ganz".

Akzeptanz

  • Frisch gepulltes Multi-Arch-:latest (z. B. NPM-Container) zeigt kein dauerhaftes "Update verfuegbar" mehr.
  • Ein echtes Remote-Update (neuer arch-Digest) wird weiterhin erkannt.
  • Single-Arch-Images korrekt; bei Unaufloesbarkeit unknown, kein False-Positive.
  • Keine Regression im --dry-run-Fallback.

Hinweis

Rust ist im Container nicht baubar → sauber schreiben, Sebastian baut/testet lokal (idealer Test: der NPM-Container, der das Symptom zeigt).

Branch

fix/compose-check-multiarch-digest

## Konkreter Fix (Weg B — arch-Digest gegen arch-Digest) Bestaetigt @ aktuell: `compose_check` (VM-Guest-Agent/src/collect/mod.rs:362-363) vergleicht - `cur` = `RepoDigests[0]` → **arch-spezifischer** Manifest-Digest der lokal gezogenen Plattform - `rem` = `imagetools inspect --format {{.Manifest.Digest}}` → bei Multi-Arch-`:latest` der **Index-Digest** (Manifest-Liste ueber ALLE Archs) → die beiden sind per Definition nie gleich → `cur != rem` (:380) immer true → **dauerhafter False-Positive**. Die umgebende Logik (leere Digests → `unknown`/Null) ist korrekt; nur der Vergleich vergleicht Index gegen Arch. ### Fix Remote ebenfalls den **arch-spezifischen** Digest fuer die **lokale Plattform** aufloesen, statt des Index: 1. Lokale Plattform bestimmen: `docker version -f '{{.Server.Os}}/{{.Server.Arch}}'` (bzw. os/arch/variant; arm braucht ggf. `v7`/`v8`). 2. Remote die **Manifest-Liste roh** holen: `docker buildx imagetools inspect "$img" --raw` (JSON: `manifests[]` mit `platform.{os,architecture,variant}` + `digest`). 3. Aus der Liste den Eintrag mit passender Plattform herauspicken → dessen `digest` = `rem`. 4. Vergleich `cur != rem` wie bisher. ### Sicherheitsnetz (kein neuer False-Positive) - **Single-Arch-Image** (kein `manifests[]`, sondern direkt ein Manifest): `--raw` liefert keinen Index → dann `rem` = der direkte Manifest-Digest (`{{.Manifest.Digest}}` wie bisher ist hier korrekt). Erkennen am `mediaType` (image.index vs image.manifest) bzw. am Fehlen von `manifests[]`. - **Plattform nicht in der Liste / Parsing/Tool schlaegt fehl / buildx fehlt**: NICHT `update_available=true` setzen, sondern **`unknown` (Null)** — lieber kein Signal als ein falsches. - Der bestehende Pull-`--dry-run`-Fallback bleibt fuer "compose/buildx fehlt ganz". ### Akzeptanz - Frisch gepulltes Multi-Arch-`:latest` (z. B. NPM-Container) zeigt **kein** dauerhaftes "Update verfuegbar" mehr. - Ein echtes Remote-Update (neuer arch-Digest) wird weiterhin erkannt. - Single-Arch-Images korrekt; bei Unaufloesbarkeit `unknown`, kein False-Positive. - Keine Regression im `--dry-run`-Fallback. ### Hinweis Rust ist im Container nicht baubar → sauber schreiben, Sebastian baut/testet lokal (idealer Test: der NPM-Container, der das Symptom zeigt). ### Branch `fix/compose-check-multiarch-digest`
Sign in to join this conversation.
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
chinux/theProx#91
No description provided.