From faa37e7c58994066744a1e06f7cf80ceed3312d7 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 15 Sep 2026 11:29:20 +0200 Subject: [PATCH] fix(ci): restore preflight image cleanup marker and remove obsolete publish-container tests - Restore build_attempted=1 in ci-preflight.sh so the exit trap removes the temporary image tag - Remove publish-container.test.ts and its harness (publication workflow and script were removed in fff284aa) - Update deploy-workflow-contract and docker-build-contract tests to assert that publication has been removed --- scripts/ci-preflight.sh | 1 + src/lib/deploy-workflow-contract.test.ts | 15 +--- src/lib/docker-build-contract.test.ts | 10 +-- src/lib/publish-container.test.ts | 99 ------------------------ src/test/publish-container-harness.sh | 29 ------- 5 files changed, 6 insertions(+), 148 deletions(-) delete mode 100644 src/lib/publish-container.test.ts delete mode 100644 src/test/publish-container-harness.sh diff --git a/scripts/ci-preflight.sh b/scripts/ci-preflight.sh index cea83037..348460ab 100644 --- a/scripts/ci-preflight.sh +++ b/scripts/ci-preflight.sh @@ -38,6 +38,7 @@ pnpm install --frozen-lockfile pnpm exec playwright install chromium export NEWS_E2E_IMAGE="$image" export NEWS_E2E_RELEASE="$sha" +build_attempted=1 DOCKER_BUILDKIT=1 docker build --network=host --progress=plain \ --build-arg NEXT_DEPLOYMENT_ID="$sha" -t "$image" . NEWS_E2E_IMAGE="$image" NEWS_E2E_RELEASE="$sha" node --import tsx e2e/news-real/run.ts \ No newline at end of file diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts index 192e1532..0e0834ad 100644 --- a/src/lib/deploy-workflow-contract.test.ts +++ b/src/lib/deploy-workflow-contract.test.ts @@ -54,16 +54,7 @@ it("builds the checked out source without fetching a moving remote branch", () = expect(dockerfile).not.toMatch(/^RUN\s+git\s+(?:pull|fetch|clone)\b/m); }); -it("publishes commit images after successful main deployment and preserves manual retries", () => { - const publish = workflow.slice(workflow.indexOf("\n publish-container:")); - expect(publish).toContain("needs: deploy"); - expect(publish).toContain("gitea.event_name == 'push'"); - expect(publish).toContain("gitea.ref_name == 'main'"); - expect(publish).toContain("gitea.ref_name == 'master'"); - expect(publish).toContain("bash scripts/publish-container.sh"); - expect(publish).toContain("secrets.CONTAINER_REGISTRY_USER"); - expect(publish).toContain("secrets.CONTAINER_REGISTRY_TOKEN"); - const manual = readFileSync(".gitea/workflows/container.yaml", "utf8"); - expect(manual).toContain("workflow_dispatch:"); - expect(manual).not.toMatch(/^ {2}push:/m); +it("no longer publishes container images in CI", () => { + expect(workflow).not.toContain("publish-container"); + expect(workflow).not.toContain("publish-container.sh"); }); diff --git a/src/lib/docker-build-contract.test.ts b/src/lib/docker-build-contract.test.ts index 6e440de4..5028655f 100644 --- a/src/lib/docker-build-contract.test.ts +++ b/src/lib/docker-build-contract.test.ts @@ -58,14 +58,8 @@ it("builds with fixtures and excludes installation secrets from every stage", () expect(updater).toContain("target=/app/.env,readonly"); expect(updater).toContain("--target migrations"); }); -it("verifies portability before publishing and uses committed build context", () => { - const publish = readFileSync("scripts/publish-container.sh", "utf8"); - expect(publish).toContain("git archive HEAD"); - expect( - publish.indexOf("node scripts/verify-portable-image.mjs"), - ).toBeLessThan(publish.indexOf("regctl image import")); - expect(publish).toContain("--password-stdin"); - expect(publish).not.toContain(":latest"); +it("does not reference a container publication script", () => { + expect(() => readFileSync("scripts/publish-container.sh", "utf8")).toThrow(); }); it("does not prerender installation metadata into a shared image", () => { diff --git a/src/lib/publish-container.test.ts b/src/lib/publish-container.test.ts deleted file mode 100644 index e03cce87..00000000 --- a/src/lib/publish-container.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { spawnSync } from "node:child_process"; -import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { delimiter, dirname, join, resolve } from "node:path"; -import { describe, expect, it } from "vitest"; - -const root = process.cwd(); -const bash = - process.platform === "win32" - ? ((process.env.PATH ?? "") - .split(delimiter) - .flatMap((dir) => [ - join(dir, "bash.exe"), - join(dirname(dir), "bin", "bash.exe"), - join(dirname(dirname(dir)), "bin", "bash.exe"), - ]) - .find((path) => existsSync(path)) ?? "bash") - : "bash"; -const sha = "a".repeat(40); -function simulate(scenario: string, namespace = "", expectedStatus = 0) { - const dir = mkdtempSync(join(tmpdir(), "cms-publish-test-")); - try { - const result = spawnSync( - bash, - [resolve(root, "scripts/publish-container.sh")], - { - cwd: dir, - encoding: "utf8", - timeout: 10000, - env: { - ...process.env, - BASH_ENV: resolve(root, "src/test/publish-container-harness.sh"), - TEST_DIR: dir.replaceAll("\\", "/"), - TEST_SHA: sha, - SCENARIO: scenario, - REGISTRY_SERVER: "https://registry.invalid", - REGISTRY_REPOSITORY: "owner/cms", - REGISTRY_USER: "Simo", - REGISTRY_NAMESPACE: namespace, - REGISTRY_TOKEN: "fixture-only", - }, - }, - ); - if (result.error) throw result.error; - expect(result.status, result.stdout + result.stderr).toBe(expectedStatus); - return readFileSync(join(dir, "calls"), "utf8"); - } finally { - rmSync(dir, { recursive: true, force: true }); - } -} -describe("verified application image reuse", () => { - it("reuses only the exact image digest that passed deployment checks", () => { - const calls = simulate("verified"); - expect(calls).toContain( - `docker tag sha256:candidate registry.invalid/simo/cms:${sha}`, - ); - expect(calls).not.toContain("docker build --network=host --build-arg"); - expect( - calls.indexOf("verify scripts/verify-portable-image.mjs"), - ).toBeLessThan(calls.indexOf("regctl image copy")); - }); - it.each(["missing", "mismatch"])( - "builds committed source when verification marker is %s", - (scenario) => { - const calls = simulate(scenario); - expect(calls).toContain("docker build --network=host --build-arg"); - expect(calls).not.toContain("docker tag sha256:candidate"); - }, - ); -}); - -it("uses the token account namespace instead of the repository owner", () => { - const calls = simulate("verified"); - expect(calls).toContain(`registry.invalid/simo/cms:${sha}\n`); - expect(calls).not.toContain("registry.invalid/owner/cms"); -}); -it("supports an explicit organization namespace", () => { - const calls = simulate("verified", "My-Org"); - expect(calls).toContain(`registry.invalid/my-org/cms:${sha}\n`); -}); - -it("bounds uploads and verifies both published image configs", () => { - const calls = simulate("verified"); - expect(calls).toContain("--blob-chunk 8388608 --blob-max 8388608"); - expect(calls).not.toContain("docker push"); - expect(calls.match(/regctl image import/g)).toHaveLength(2); - expect(calls.match(/regctl image copy/g)).toHaveLength(2); - expect(calls.match(/regctl manifest get/g)).toHaveLength(4); - expect(calls.match(/--platform linux\/amd64/g)).toHaveLength(4); -}); -it.each(["upload-fails", "wrong-config", "bad-checksum"])( - "stops publication on %s", - (scenario) => { - const calls = simulate(scenario, "", 1); - expect(calls.match(/regctl image copy/g)?.length ?? 0).toBeLessThanOrEqual( - 1, - ); - }, -); diff --git a/src/test/publish-container-harness.sh b/src/test/publish-container-harness.sh deleted file mode 100644 index dc773485..00000000 --- a/src/test/publish-container-harness.sh +++ /dev/null @@ -1,29 +0,0 @@ -git() { if [ "$1" = rev-parse ]; then echo "$TEST_SHA"; fi; } -tar() { cat >/dev/null; } -node() { echo "verify $*" >> "$TEST_DIR/calls"; } -docker() { - echo "docker $*" >> "$TEST_DIR/calls" - if [ "$1" = login ]; then cat >/dev/null; return; fi - if [ "$1 $2" = "image inspect" ]; then - if [[ "$*" = *org.opencontainers.image.revision* ]]; then echo "$TEST_SHA" - elif [[ "${@: -1}" = *verified-* ]]; then - case "$SCENARIO" in verified) echo sha256:candidate ;; mismatch) echo sha256:other ;; *) return 1 ;; esac - else echo sha256:candidate; fi - fi -} -export -f git tar node docker - -curl() { - local output="${@: -1}" - cat > "$output" <<'MOCK' -#!/usr/bin/env bash -echo "regctl $*" >> "$TEST_DIR/calls" -if [[ "$1 $2" = "image copy" && "$SCENARIO" = upload-fails ]]; then exit 1; fi -if [[ "$1 $2" = "manifest get" ]]; then - if [[ "$SCENARIO" = wrong-config && "$3" != ocidir:* ]]; then echo sha256:wrong; else printf "sha256:%064d\n" 0; fi -fi -MOCK -} -sha256sum() { cat >/dev/null; [[ "$SCENARIO" != bad-checksum ]]; } -uname() { echo x86_64; } -export -f curl sha256sum uname