mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-16 20:02:45 +03:00
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:
69
.github/workflows/docker-publish.yml
vendored
69
.github/workflows/docker-publish.yml
vendored
@@ -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: |
|
||||
|
||||
46
tests/unit/build/docker-published-image-smoke-11835.test.ts
Normal file
46
tests/unit/build/docker-published-image-smoke-11835.test.ts
Normal 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/);
|
||||
});
|
||||
Reference in New Issue
Block a user