From 6a9a4607699c66287535d43b1587858031194f8d Mon Sep 17 00:00:00 2001 From: mr-forust Date: Sun, 27 Sep 2026 16:33:37 +0200 Subject: [PATCH] fix(deploy): let a pinning failure explain itself registry_digest was written to return an empty string for a ref the registry does not have, so render_pinned could print "cannot resolve " and stop. It could not do that. Every caller runs under set -euo pipefail, pipefail reports the rightmost non-zero stage, and the failed docker manifest inspect made the assignment itself fail, which set -e turns into an immediate exit. render_pinned therefore died silently on the first unresolvable ref: nothing on stderr, nothing on stdout, exit 1. The apply loop piped that empty stream into kubectl, so the whole deploy stopped with "error: no objects passed to apply" - kubectl guessing at a cause, with the actual reason nowhere in the log. The missing message is the reason the f54589a run looked like a network death. Reproduced against the old file with a docker stub that always fails: identical to the ac0f845 log. The || true makes the empty string reachable, and the apply loop now names the file that failed instead of letting kubectl speak. --- .gitea/workflows/deploy-lib.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/deploy-lib.sh b/.gitea/workflows/deploy-lib.sh index d765542..1f0de86 100644 --- a/.gitea/workflows/deploy-lib.sh +++ b/.gitea/workflows/deploy-lib.sh @@ -227,15 +227,19 @@ owned_registry_workloads() { # mark every workload stale forever and restart the whole cluster on every deploy. registry_digest() { local arch - arch="$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.architecture}' 2>/dev/null)" + arch="$(kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.architecture}' 2>/dev/null || true)" [ -n "$arch" ] || arch=amd64 + # The || true is load-bearing. Every caller runs under set -euo pipefail, and + # pipefail reports the rightmost non-zero stage, so a ref the registry does not + # have would abort the caller at the assignment instead of yielding an empty + # string. The callers check for empty themselves and report it by name. docker manifest inspect "$1" 2>/dev/null \ | jq -r --arg arch "$arch" ' .manifests[]? | select(.platform.os == "linux" and .platform.architecture == $arch) | .digest ' 2>/dev/null \ - | head -1 + | head -1 || true } # Rewrites our own images to immutable digests on the way into the cluster. @@ -644,12 +648,18 @@ stage_apply_k8s() { if [ "${#other_files[@]}" -gt 0 ]; then log "Applying resources (${#other_files[@]} files, our images pinned to digests)" for m in "${other_files[@]}"; do - render_pinned <"$m" | kubectl apply "${prune_opts[@]}" -f - + if ! render_pinned <"$m" | kubectl apply "${prune_opts[@]}" -f -; then + echo "ERROR: apply failed for ${m#"$REPO"/}" >&2 + exit 1 + fi done fi for k in ${KUSTOMIZE_APPS[@]+"${KUSTOMIZE_APPS[@]}"}; do log "Applying kustomize app: ${k#"$REPO"/} (our images pinned to digests)" - kubectl kustomize "$k" | render_pinned | kubectl apply -f - + if ! kubectl kustomize "$k" | render_pinned | kubectl apply -f -; then + echo "ERROR: apply failed for kustomize app ${k#"$REPO"/}" >&2 + exit 1 + fi done if [ -f "$REPO/userbot/k8s/active" ]; then log "userbot panel hook"