"manifest unknown" erklaeren - und eine Pipe-Falle beseitigen, die das

Erkennen verhindert hat

Der Pull meldete "manifest unknown". Das heisst, anders als zuvor: der
Repository-Pfad registry.checkmk.com/enterprise/check-mk-enterprise ist
richtig und die Anmeldung auch - nur das Tag 2.4.0p9 gibt es dort nicht.

Neu "image why [REF]" unterscheidet die drei Faelle anhand der Registry
statt anhand der Fehlermeldung:
    401/403  Anmeldung oder Berechtigung
    404      Repository gibt es nicht        -> image repos
    Tag      Repository ja, Tag nein         -> vorhandene Tags des
             Zweigs werden aufgelistet
"up" ruft das von selbst auf, wenn das Starten scheitert und das Image
nicht lokal vorliegt; "image pull" ebenfalls.

Beim Bauen stellte sich heraus, dass die Tag-Pruefung immer "nicht
vorhanden" meldete - auch fuer Tags, die es gibt. Ursache ist eine
Falle, die das ganze Projekt betraf:

    printf ... | grep -q muster

"grep -q" endet beim ersten Treffer. Der Erzeuger bekommt dann SIGPIPE,
und unter "set -o pipefail" gilt die ganze Pipe als fehlgeschlagen -
aber nur, wenn er noch am Schreiben war. Bei drei Zeilen faellt das nie
auf, bei 2393 Tags immer. Nachgestellt:

    set -euo pipefail; seq 1 500000 | grep -qx 3   -> schlaegt fehl

Betroffen waren zwoelf Stellen, darunter zwei, die ernster sind als die
Tag-Liste: bundle_read prueft mit "tar -tzf ... | grep -qx", ob ein
Backup ein Bundle ist. Bei einem grossen Archiv haette ein gueltiges
Bundle als "kein Bundle" gegolten - also ein stiller Fehler bei Restore
und Import genau dann, wenn das Backup gross ist. Alle zwoelf laufen
jetzt in einer Subshell ohne pipefail.

Der Selbsttest hat dafuer eine eigene Pruefung bekommen, damit die
Schreibweise nicht zurueckkehrt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-20 18:44:08 +02:00
co-authored by Claude Opus 5
parent a8fbc3f557
commit 7fb0c6ea9e
5 changed files with 146 additions and 25 deletions
+137 -18
View File
@@ -119,6 +119,8 @@ KOMMANDOS - Checkmk-Image beschaffen
--limit N hoechstens N Eintraege (Vorgabe 25), --all fuer alle
--user U --password P statt der Werte aus der .env
--bare nur die nackte Liste, ohne Rahmen
image why [REF] erklaeren, warum sich eine Referenz nicht holen
laesst (ohne REF: die aus CMK_IMAGE)
image repos [Opt.] herausfinden, welche Repositories die Registry
wirklich anbietet (Katalog + uebliche Pfade)
image test [Opt.] Zugangsdaten pruefen, ohne etwas herunterzuladen
@@ -244,7 +246,13 @@ bundle_read() {
B_BUNDLE_TYPE=""; B_ARCHIVE_SIZE=""; B_OMD_BACKUP_OPTS=""; B_SOURCE_OS=""
B_IS_BUNDLE=0
if tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/MANIFEST"; then
# ACHTUNG: "grep -q" beendet sich beim ersten Treffer. Der Erzeuger
# bekommt dann SIGPIPE, und unter "set -o pipefail" gilt die ganze
# Pipe als fehlgeschlagen - aber nur, wenn er noch am Schreiben war,
# also abhaengig von der Datenmenge. Bei einem grossen Archiv wuerde
# ein vorhandenes Bundle dadurch als "kein Bundle" gelten. Deshalb
# solche Pipes immer in einer Subshell ohne pipefail ausfuehren.
if ( set +o pipefail; tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/MANIFEST" ); then
B_IS_BUNDLE=1
local mf; mf="$(mktemp)"
tar -xzOf "$file" "$BUNDLE_DIRNAME/MANIFEST" >"$mf"
@@ -324,7 +332,7 @@ bundle_verify() {
# Nutzdaten (omd-Archiv) aus einem Bundle nach stdout schreiben
bundle_extract_payload() {
local file="$1"
if tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/site.tar.gz"; then
if ( set +o pipefail; tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/site.tar.gz" ); then
tar -xzOf "$file" "$BUNDLE_DIRNAME/site.tar.gz"
else
cat "$file"
@@ -413,8 +421,8 @@ image_load_file() {
[ -f "$file" ] || die "Datei nicht gefunden: $file"
# Ist das ueberhaupt ein Container-Image-Archiv (docker-archive/OCI)?
if ! tar -tzf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$'; then
if tar -tf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$'; then
if ! ( set +o pipefail; tar -tzf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$' ); then
if ( set +o pipefail; tar -tf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$' ); then
:
else
err "'$file' enthaelt kein manifest.json / oci-layout."
@@ -547,7 +555,7 @@ archive_docker_file() { # edition version -> Dateiname auf stdout
[ -n "$names" ] || return 1
local ed
for ed in $(edition_aliases "$edition"); do
if printf '%s\n' "$names" | grep -qx "check-mk-${ed}-docker-${version}.tar.gz"; then
if ( set +o pipefail; printf '%s\n' "$names" | grep -qx "check-mk-${ed}-docker-${version}.tar.gz" ); then
printf 'check-mk-%s-docker-%s.tar.gz\n' "$ed" "$version"; return 0
fi
done
@@ -648,11 +656,8 @@ EOF
log "Hole Image: $ref"
if ! $RUNTIME pull "$ref"; then
err "Das Image '$ref' konnte nicht geholt werden."
if [ "$reg" != "docker.io" ] && [ -n "$user" ]; then
warn "Die Anmeldung hat geklappt, der Pfad oder das Tag aber nicht.
Ich sehe nach, was diese Registry tatsaechlich anbietet ..."
image_cmd_repos --edition "$edition" --registry "$reg" \
--user "$user" --password "$pass" || true
if [ "$reg" != "docker.io" ] || [ -n "$user" ]; then
image_diagnose_ref "$ref" || true
return 1
fi
if [ "$edition" = enterprise ]; then
@@ -1215,6 +1220,88 @@ image_cmd_test() {
return 1
}
# Warum laesst sich eine Image-Referenz nicht holen?
#
# Die Fehlermeldungen der Runtime sind eindeutig, aber unerklaert:
# "manifest unknown" -> Repository ist da, das TAG nicht
# "repository name unknown" -> Repository gibt es nicht
# "unauthorized" -> Anmeldung oder Berechtigung
# Diese Funktion fragt die Registry direkt und sagt, welcher Fall vorliegt.
image_diagnose_ref() { # ref
local ref="$1"
command -v curl >/dev/null 2>&1 || return 0
local tag regpart reg repo api
tag="${ref##*:}"; regpart="${ref%:*}"
case "$regpart" in
*/*) reg="${regpart%%/*}"; repo="${regpart#*/}" ;;
*) reg="docker.io"; repo="$regpart" ;;
esac
[ "$tag" = "$ref" ] && tag=""
api="$(registry_api_host "$reg")"
local u="" p=""
case "$reg" in
docker.io|index.docker.io|registry-1.docker.io) : ;;
*) u="${CMK_REGISTRY_USER:-}"; p="${CMK_REGISTRY_PASSWORD:-}" ;;
esac
printf '\n%sWoran liegt es?%s\n' "$C_BLD" "$C_RST"
printf ' Registry : %s\n' "$reg"
printf ' Repository : %s\n' "$repo"
printf ' Tag : %s\n\n' "${tag:-<keins>}"
local line code
line="$(registry_repo_status "$api" "$repo" "$u" "$p")"
code="${line%% *}"
case "$code" in
401|403)
test_result fail "Anmeldung oder Berechtigung (HTTP $code)"
printf ' Pruefen: %s image test --only registry\n' "$SELF"
return 1 ;;
404)
test_result fail "Dieses Repository gibt es dort nicht (HTTP 404)"
printf ' Welche es gibt: %s image repos --edition %s\n' "$SELF" "${CMK_EDITION:-enterprise}"
return 1 ;;
200) : ;;
*) test_result warn "Registry antwortet unerwartet (HTTP $code)"; return 1 ;;
esac
test_result ok "Repository vorhanden und zugaenglich"
local -a tags=()
mapfile -t tags < <(registry_tags "$api" "$repo" "$u" "$p" | sed '/^$/d')
if [ ${#tags[@]} -eq 0 ]; then
test_result warn "Es liess sich keine Tag-Liste lesen."
return 1
fi
if ( set +o pipefail; printf '%s\n' "${tags[@]}" | grep -qxF "$tag" ); then
test_result ok "Das Tag '$tag' ist vorhanden - der Fehler hat eine andere Ursache."
return 0
fi
test_result fail "Das Tag '$tag' gibt es in diesem Repository NICHT."
printf ' Genau das meint die Meldung "manifest unknown".\n\n'
# Naheliegende Tags zeigen: gleicher Zweig, sonst die neuesten
local branch="${tag%%p*}"
local -a near=()
mapfile -t near < <(printf '%s\n' "${tags[@]}" | grep -F "$branch" | sort -rV | head -n 12)
if [ ${#near[@]} -gt 0 ]; then
printf ' %sVorhanden im Zweig %s%s\n' "$C_BLD" "$branch" "$C_RST"
else
mapfile -t near < <(printf '%s\n' "${tags[@]}" | sort -rV | head -n 12)
printf ' %sVorhanden (neueste)%s\n' "$C_BLD" "$C_RST"
fi
printf ' %s\n' "${near[@]}"
printf '\n Uebernehmen mit:\n'
printf ' %s image use %s/%s:<tag> --remember\n' "$SELF" "$reg" "$repo"
printf ' %s up\n' "$SELF"
printf '\n Alle Tags: %s image versions --source registry --edition %s --all\n\n' \
"$SELF" "${CMK_EDITION:-enterprise}"
return 1
}
# ---------------------------------------------------------------------
# image versions - verfuegbare Versionen auflisten
#
@@ -1517,8 +1604,9 @@ cmd_image() {
test|check) image_cmd_test "$@" ;;
versions|tags) image_cmd_versions "$@" ;;
repos|repo) image_cmd_repos "$@" ;;
why|diagnose) image_diagnose_ref "${1:-${CMK_IMAGE:-}}" ;;
*) die "Unbekanntes Unterkommando 'image $sub'.
Moeglich: status | list | versions | repos | test | pull | download | load | use" ;;
Moeglich: status | list | versions | repos | why | test | pull | download | load | use" ;;
esac
}
@@ -1544,7 +1632,15 @@ cmd_up() {
else
log "Starte Stack (${RUNTIME}) - ohne NGINX Proxy Manager (NPM_ENABLED=off) ..."
fi
compose up -d "${svc[@]}"
if ! compose up -d "${svc[@]}"; then
echo
err "Der Stack konnte nicht gestartet werden."
# Haeufigste Ursache: das Image laesst sich nicht holen.
if [ -n "${CMK_IMAGE:-}" ] && ! image_exists_local "$CMK_IMAGE"; then
image_diagnose_ref "$CMK_IMAGE" || true
fi
return 1
fi
npm_enabled || npm_teardown_container
ok "Stack gestartet."
cmd_status
@@ -2177,7 +2273,7 @@ do_restore() {
if [ -n "$B_CMK_VERSION" ]; then
local installed
installed="$(cmk_versions_installed)"
if ! printf '%s\n' "$installed" | grep -qxF "$B_CMK_VERSION"; then
if ! ( set +o pipefail; printf '%s\n' "$installed" | grep -qxF "$B_CMK_VERSION" ); then
err "Die Checkmk-Version des Backups ist im Container nicht installiert."
printf ' benoetigt : %s\n' "$B_CMK_VERSION"
printf ' installiert : %s\n' "$(printf '%s' "$installed" | tr '\n' ' ')"
@@ -2551,7 +2647,7 @@ EOF
port_in_use() {
command -v ss >/dev/null 2>&1 || return 1
ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$"
( set +o pipefail; ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$" )
}
valid_port() {
@@ -3300,7 +3396,7 @@ cmd_proxy() {
local all; all="$(npm_api GET /api/nginx/proxy-hosts)"
local i
for i in $(json_ids "$all"); do
if npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\""; then pid="$i"; break; fi
if ( set +o pipefail; npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\"" ); then pid="$i"; break; fi
done
[ -n "$pid" ] || die "Kein Proxy-Host fuer '$dom' gefunden."
fi
@@ -3804,7 +3900,7 @@ gui_job_edit() { # name (leer = neu)
while IFS= read -r sname; do
[ -n "$sname" ] || continue
items+=("$sname" "einzelne Site" \
"$(printf '%s' ",$sites," | grep -q ",$sname," && echo on || echo off)")
"$( ( set +o pipefail; printf '%s' ",$sites," | grep -q ",$sname," ) && echo on || echo off)")
done < <(cmk_sites)
local chosen
chosen="$(ui_checklist "Sites" "Was soll dieser Job sichern?\n(ALL schliesst alle anderen ein)" "${items[@]}")" || return 0
@@ -4822,7 +4918,30 @@ cmd_selftest() {
printf ' "set -e" das Skript. Besser als if-Block schreiben.\n'
fi
printf '\n%s5. Zeichensatz fuer die Oberflaeche%s\n' "$C_BLD" "$C_RST"
printf '\n%s5. Keine "| grep -q" ohne Schutz vor SIGPIPE%s\n' "$C_BLD" "$C_RST"
n=0
for f in "${files[@]}"; do
[ -f "$f" ] || continue
while IFS= read -r line; do
[ -n "$line" ] || continue
printf ' %s[FEHLER]%s %s:%s\n' "$C_RED" "$C_RST" "$(basename "$f")" "$line"
n=$((n+1))
done < <(grep -nE '\|[[:space:]]*grep -[a-zA-Z]*q' "$f" \
| grep -v 'set +o pipefail' \
| grep -vE '^[0-9]+:[[:space:]]*#' \
| grep -v 'printf' | cut -c1-72 || true)
done
if [ "$n" -eq 0 ]; then
printf ' %s[ OK ]%s alle solchen Pipes laufen ohne pipefail\n' "$C_GRN" "$C_RST"
else
printf ' "grep -q" endet beim ersten Treffer; der Erzeuger bekommt\n'
printf ' SIGPIPE und unter "set -o pipefail" gilt die Pipe als\n'
printf ' fehlgeschlagen - abhaengig von der Datenmenge. Schreibweise:\n'
printf ' if ( set +o pipefail; erzeuger | grep -q muster ); then\n'
rc=1
fi
printf '\n%s6. Zeichensatz fuer die Oberflaeche%s\n' "$C_BLD" "$C_RST"
local before="${LC_ALL:-${LC_CTYPE:-${LANG:-<leer>}}}"
ui_fix_locale || true
printf ' Locale vorher : %s\n' "$before"
@@ -4835,7 +4954,7 @@ cmd_selftest() {
"$([ -n "${UI_LOCALE_NOTE:-}" ] && echo " (${UI_LOCALE_NOTE})")"
fi
printf '\n%s6. Einstellungstabelle%s\n' "$C_BLD" "$C_RST"
printf '\n%s7. Einstellungstabelle%s\n' "$C_BLD" "$C_RST"
n="$(settings_defs | awk -F'|' 'NF!=6' | wc -l)"
if [ "$n" -eq 0 ]; then
printf ' %s[ OK ]%s alle Zeilen haben sechs Felder\n' "$C_GRN" "$C_RST"