fix(docker): smoke-test images before promoting latest (#13761)

Merged. The ordering is what makes this fix real: the version manifests are published first, the smoke test runs against the published image, and only then are the floating `latest` tags promoted — so a broken build can no longer become `latest`. Both Dockerfiles carry a `HEALTHCHECK`, and the 30×2s window covers the 15s `start-period`.

Validated as a combined board first (this PR merged with the 21 siblings of the same wave on the release tip): eslint with the frozen suppressions, typecheck:core, check:open-sse-typecheck, complexity, cognitive-complexity, changelog-integrity, i18n new-key coverage, docs-counts, docs-sync, migration-numbering, provider-consistency and a duplicate-identifier audit all green, plus 176 passing / 0 failing focused node:test cases across the 25 test files the wave touches and the dashboard test under Vitest (2/0). Then re-validated alone on the fresh tip before this merge: conflicts re-resolved, file sizes rebaselined for this PR's own growth, eslint and this PR's focused tests re-run.

Thanks for closing #11835 properly instead of just adding a check after the fact.
This commit is contained in:
lorenzozane
2026-09-17 00:36:18 +08:00
committed by GitHub
parent 20c6d89c2d
commit c8d102179d
2 changed files with 103 additions and 12 deletions

View File

@@ -411,16 +411,12 @@ jobs:
path: /tmp/digests/bun-web
merge-multiple: true
- name: Create Docker Hub manifest
- name: Create Docker Hub version manifests
run: |
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
fi
local refs=()
while IFS= read -r digest_file; do
refs+=("${image}@sha256:$(basename "$digest_file")")
@@ -433,7 +429,7 @@ jobs:
echo "No image digests in $dir" >&2
exit 1
fi
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
docker buildx imagetools create -t "${image}:${VERSION}${suffix}" "${refs[@]}"
}
create_manifest "${IMAGE_NAME}" "" /tmp/digests/base
@@ -441,16 +437,12 @@ jobs:
create_manifest "${IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Create GHCR manifest
- name: Create GHCR version manifests
run: |
set -euo pipefail
create_manifest() {
local image="$1" suffix="$2" dir="$3" optional="${4:-}"
local tags=(-t "${image}:${VERSION}${suffix}")
if [ "$PROMOTE_LATEST" = "true" ]; then
tags+=(-t "${image}:latest${suffix}")
fi
local refs=()
while IFS= read -r digest_file; do
refs+=("${image}@sha256:$(basename "$digest_file")")
@@ -463,7 +455,7 @@ jobs:
echo "No image digests in $dir" >&2
exit 1
fi
docker buildx imagetools create "${tags[@]}" "${refs[@]}"
docker buildx imagetools create -t "${image}:${VERSION}${suffix}" "${refs[@]}"
}
create_manifest "${GHCR_IMAGE_NAME}" "" /tmp/digests/base
@@ -471,6 +463,59 @@ jobs:
create_manifest "${GHCR_IMAGE_NAME}" "-bun" /tmp/digests/bun-base optional
create_manifest "${GHCR_IMAGE_NAME}" "-web-bun" /tmp/digests/bun-web optional
- name: Smoke-test published Docker image
if: needs.prepare.outputs.version != 'main'
run: |
set -euo pipefail
container="omniroute-smoke-${VERSION//[^a-zA-Z0-9_.-]/-}"
trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT
docker run --detach --name "$container" "${IMAGE_NAME}:${VERSION}"
for attempt in $(seq 1 30); do
status="$(docker inspect --format '{{.State.Health.Status}}' "$container")"
if [ "$status" = "healthy" ]; then
exit 0
fi
if [ "$status" = "unhealthy" ] || [ "$(docker inspect --format '{{.State.Status}}' "$container")" = "exited" ]; then
docker logs "$container"
exit 1
fi
sleep 2
done
docker logs "$container"
exit 1
- name: Promote Docker Hub latest tags
if: needs.prepare.outputs.promote_latest == 'true'
run: |
set -euo pipefail
promote_tag() {
local suffix="$1"
docker buildx imagetools create -t "${IMAGE_NAME}:latest${suffix}" "${IMAGE_NAME}:${VERSION}${suffix}"
}
promote_tag ""
promote_tag "-web"
for suffix in -bun -web-bun; do
if docker buildx imagetools inspect "${IMAGE_NAME}:${VERSION}${suffix}" >/dev/null 2>&1; then
promote_tag "$suffix"
fi
done
- name: Promote GHCR latest tags
if: needs.prepare.outputs.promote_latest == 'true'
run: |
set -euo pipefail
promote_tag() {
local suffix="$1"
docker buildx imagetools create -t "${GHCR_IMAGE_NAME}:latest${suffix}" "${GHCR_IMAGE_NAME}:${VERSION}${suffix}"
}
promote_tag ""
promote_tag "-web"
for suffix in -bun -web-bun; do
if docker buildx imagetools inspect "${GHCR_IMAGE_NAME}:${VERSION}${suffix}" >/dev/null 2>&1; then
promote_tag "$suffix"
fi
done
- name: Inspect image
if: needs.prepare.outputs.version != 'main'
run: |

View File

@@ -0,0 +1,46 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(here, "../../..");
const workflow = readFileSync(
path.join(root, ".github/workflows/docker-publish.yml"),
"utf8",
);
test("published version is smoke-tested before latest promotion", () => {
const versionManifest = workflow.indexOf("Create Docker Hub version manifests");
const smoke = workflow.indexOf("Smoke-test published Docker image");
const latestPromotion = workflow.indexOf("Promote Docker Hub latest tags");
assert.ok(versionManifest >= 0, "version manifest step must exist");
assert.ok(smoke > versionManifest, "smoke test must follow version publication");
assert.ok(latestPromotion > smoke, "latest promotion must follow the smoke test");
});
test("smoke test verifies the container health status", () => {
const smoke = workflow.match(
/- name: Smoke-test published Docker image[\s\S]*?(?=\n - name:|\n - uses:|\n\s*$)/,
);
assert.ok(smoke, "published image smoke-test step must exist");
assert.match(smoke[0], /docker run/);
assert.match(smoke[0], /docker inspect/);
assert.match(smoke[0], /healthy/);
});
test("latest promotion preserves every published image flavor", () => {
const promotion = workflow.match(
/- name: Promote Docker Hub latest tags[\s\S]*?(?=\n - name: Inspect image)/,
);
assert.ok(promotion, "Docker Hub latest promotion must exist");
assert.match(promotion[0], /promote_tag ""/);
assert.match(promotion[0], /promote_tag "-web"/);
assert.match(promotion[0], /for suffix in -bun -web-bun/);
assert.match(promotion[0], /imagetools inspect/);
assert.match(workflow, /Promote GHCR latest tags/);
});