fix(ci): resume verification of immutable release images
TD-009: retry verification of an existing digest without republishing. Paired backend workflow fix: 82eccc3.
This commit is contained in:
@@ -45,15 +45,17 @@ jobs:
|
||||
run: |
|
||||
VERSION="${RELEASE_TAG#v}"
|
||||
if docker manifest inspect "$IMAGE:$VERSION" >/dev/null 2>manifest-error.log; then
|
||||
echo 'Refusing to overwrite an existing release tag'; exit 1
|
||||
DIGEST="$(docker buildx imagetools inspect "$IMAGE:$VERSION" --format '{{.Manifest.Digest}}')"
|
||||
else
|
||||
if ! grep -Eq 'manifest unknown|no such manifest' manifest-error.log; then
|
||||
echo 'Cannot prove tag absence; refusing publication'; exit 1
|
||||
fi
|
||||
docker buildx build --target production --build-arg VERSION="$VERSION" --build-arg REVISION="$REVISION" --tag "$IMAGE:$VERSION" --metadata-file image-metadata.json --push .
|
||||
DIGEST="$(node --input-type=commonjs -p 'require("./image-metadata.json")["containerimage.digest"]')"
|
||||
fi
|
||||
if ! grep -Eq 'manifest unknown|no such manifest' manifest-error.log; then
|
||||
echo 'Cannot prove tag absence; refusing publication'; exit 1
|
||||
fi
|
||||
docker buildx build --target production --build-arg VERSION="$VERSION" --build-arg REVISION="$REVISION" --tag "$IMAGE:$VERSION" --metadata-file image-metadata.json --push .
|
||||
DIGEST="$(node --input-type=commonjs -p 'require("./image-metadata.json")["containerimage.digest"]')"
|
||||
docker pull "$IMAGE@$DIGEST"
|
||||
test "$(docker image inspect "$IMAGE@$DIGEST" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$REVISION"
|
||||
test "$(docker image inspect "$IMAGE@$DIGEST" --format '{{index .Config.Labels "org.opencontainers.image.version"}}')" = "$VERSION"
|
||||
docker run --rm --entrypoint sh "$IMAGE@$DIGEST" -c 'test -s /usr/share/nginx/html/index.html && nginx -t && nginx && wget -q -O /dev/null http://127.0.0.1:8080/'
|
||||
printf 'Frontend image: %s@%s\n' "$IMAGE" "$DIGEST"
|
||||
- name: Remove registry session
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
const { test } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const {
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
chmodSync,
|
||||
rmSync,
|
||||
} = require("node:fs");
|
||||
const { tmpdir } = require("node:os");
|
||||
const { join, resolve } = require("node:path");
|
||||
const EXECUTABLE_MODE = 0o755;
|
||||
const { spawnSync } = require("node:child_process");
|
||||
|
||||
test("immutable publication resumes exact digest checks after post-push failure", () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "wl-image-retry-"));
|
||||
const workflow = readFileSync(
|
||||
resolve(__dirname, "../.gitea/workflows/image.yml"),
|
||||
"utf8",
|
||||
);
|
||||
const block = workflow
|
||||
.split(" - name: Publish immutable release tag\n run: |\n")[1]
|
||||
.split(" - name:")[0]
|
||||
.replace(/^ {10}/gm, "");
|
||||
const double = `#!${process.execPath}
|
||||
const fs = require('node:fs');
|
||||
const args = process.argv.slice(2).join(' ');
|
||||
fs.appendFileSync('calls', args + '\\n');
|
||||
if (args.startsWith('manifest inspect')) {
|
||||
if (!fs.existsSync('published')) { console.error('manifest unknown'); process.exit(1); }
|
||||
} else if (args.startsWith('buildx imagetools inspect')) {
|
||||
console.log('sha256:exact');
|
||||
} else if (args.startsWith('buildx build')) {
|
||||
fs.writeFileSync('published', 'yes');
|
||||
fs.writeFileSync('image-metadata.json', JSON.stringify({'containerimage.digest': 'sha256:exact'}));
|
||||
} else if (args.startsWith('pull')) {
|
||||
if (!fs.existsSync('pull-failed')) { fs.writeFileSync('pull-failed', 'yes'); process.exit(1); }
|
||||
} else if (args.startsWith('image inspect')) {
|
||||
console.log(args.includes('image.version') ? '1.2.3' : process.env.BAD_REVISION ? 'other' : 'commit');
|
||||
} else if (args.startsWith('run') && process.env.BAD_SMOKE) process.exit(1);
|
||||
`;
|
||||
try {
|
||||
for (const name of ["docker", "npm"]) {
|
||||
writeFileSync(join(directory, name), double);
|
||||
chmodSync(join(directory, name), EXECUTABLE_MODE);
|
||||
}
|
||||
const run = (extra = {}) =>
|
||||
spawnSync("/bin/sh", ["-ec", block], {
|
||||
cwd: directory,
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${directory}:${process.env.PATH}`,
|
||||
IMAGE: "synthetic/image",
|
||||
RELEASE_TAG: "v1.2.3",
|
||||
REVISION: "commit",
|
||||
...extra,
|
||||
},
|
||||
});
|
||||
assert.notEqual(run().status, 0);
|
||||
const retry = run();
|
||||
assert.equal(retry.status, 0, retry.stdout + retry.stderr);
|
||||
assert.notEqual(run({ BAD_REVISION: "1" }).status, 0);
|
||||
assert.notEqual(run({ BAD_SMOKE: "1" }).status, 0);
|
||||
const calls = readFileSync(join(directory, "calls"), "utf8");
|
||||
assert.equal(
|
||||
calls.split("\n").filter((line) => line.startsWith("buildx build"))
|
||||
.length,
|
||||
1,
|
||||
);
|
||||
assert.ok(calls.includes("synthetic/image@sha256:exact"));
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user