chore(release): drop obsolete Docker status remnants

This commit is contained in:
Dallin Romney
2026-08-13 14:01:30 +08:00
parent ecc9a8da3b
commit 5bdbe4ec11
2 changed files with 3 additions and 116 deletions
+1 -108
View File
@@ -1,7 +1,6 @@
#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import process from "node:process";
import { parseArgs } from "node:util";
import { isDirectRunUrl } from "./lib/direct-run.mjs";
@@ -19,106 +18,6 @@ const VARIANTS = Object.freeze([
{ aliasKey: "slim", suffix: "-slim" },
{ aliasKey: "browser", suffix: "-browser" },
]);
const DOCKER_PUBLICATION_STATUS_DESCRIPTION =
"Verified GHCR + Docker Hub images, attestations, platforms, and channel aliases.";
const DOCKER_PUBLICATION_STATUS_PREFIX = "openclaw/docker-release";
/** @typedef {{ repository: string; sourceSha: string; version: string }} DockerPublicationIdentity */
/** @typedef {DockerPublicationIdentity & { runId: number | string }} DockerPublicationStatusParams */
/** @typedef {{ context: string; description: string; state: "success"; target_url: string }} DockerPublicationStatus */
/**
* @typedef {object} GitHubCommitStatus
* @property {unknown} [context]
* @property {{ login?: unknown }} [creator]
* @property {unknown} [description]
* @property {unknown} [state]
* @property {unknown} [target_url]
* @property {unknown} [url]
*/
/** @param {DockerPublicationIdentity} params */
function requireExtendedStableStatusIdentity({ version, repository, sourceSha }) {
const policy = resolveDockerReleasePolicy(version);
if (policy.channel !== "extended-stable") {
throw new Error(`Docker completion status is only valid for extended-stable; got ${version}.`);
}
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repository)) {
throw new Error(`Invalid GitHub repository identity ${JSON.stringify(repository)}.`);
}
if (!/^[a-f0-9]{40}$/u.test(sourceSha)) {
throw new Error("Docker completion status requires a full lowercase source SHA.");
}
return policy;
}
/**
* Build the durable commit status written only after Docker verification completes.
*
* @param {DockerPublicationStatusParams} params
* @returns {DockerPublicationStatus}
*/
export function createDockerPublicationStatus({ version, repository, sourceSha, runId }) {
const policy = requireExtendedStableStatusIdentity({ version, repository, sourceSha });
if (!/^[1-9][0-9]*$/u.test(String(runId))) {
throw new Error("Docker completion status requires a positive workflow run ID.");
}
return {
context: `${DOCKER_PUBLICATION_STATUS_PREFIX}/${policy.version}`,
description: DOCKER_PUBLICATION_STATUS_DESCRIPTION,
state: "success",
target_url: `https://github.com/${repository}/actions/runs/${runId}`,
};
}
/**
* Resolve the newest canonical Docker completion status from GitHub's
* reverse-chronological commit-status history.
*
* @param {DockerPublicationIdentity & { statuses: unknown }} params
* @returns {{ runId: string; targetUrl: string } | null}
*/
export function findDockerPublicationStatus({ statuses, version, repository, sourceSha }) {
const expected = createDockerPublicationStatus({
version,
repository,
sourceSha,
runId: 1,
});
if (!Array.isArray(statuses)) {
throw new Error("GitHub commit status history must be an array.");
}
// GitHub returns newest first. Validate that matching record directly so a
// malformed retry cannot be bypassed by an older canonical success.
const status = /** @type {GitHubCommitStatus[]} */ (statuses).find(
(candidate) =>
typeof candidate?.context === "string" &&
candidate.context.toLowerCase() === expected.context.toLowerCase(),
);
if (!status) {
return null;
}
const expectedStatusUrl = `https://api.github.com/repos/${repository}/statuses/${sourceSha}`;
const targetUrl = typeof status.target_url === "string" ? status.target_url : "";
const targetMatch = new RegExp(
`^https://github\\.com/${repository.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/actions/runs/([1-9][0-9]*)$`,
"u",
).exec(targetUrl);
if (
status.state !== expected.state ||
status.context !== expected.context ||
status.description !== expected.description ||
status.creator?.login !== "github-actions[bot]" ||
status.url !== expectedStatusUrl ||
!targetMatch
) {
throw new Error(`Docker completion status ${expected.context} is not canonical.`);
}
return {
runId: targetMatch[1],
targetUrl,
};
}
/** @typedef {{ imageTagSuffix?: string; images: string[]; version: string }} DockerPromotionParams */
/**
* @typedef {object} DockerExecOptions
@@ -383,9 +282,7 @@ export function promoteDockerChannel({ version, imageTagSuffix = "", images }, o
function printHelp() {
console.log(
`Usage: node scripts/docker-channel-promote.mjs --version YYYY.M.P --image REGISTRY/IMAGE [--image REGISTRY/IMAGE] [--image-tag-suffix -rYYYYMMDD] [--allow-rollback]
node scripts/docker-channel-promote.mjs --status-payload --version YYYY.M.P --repository OWNER/REPO --source-sha SHA --run-id ID
node scripts/docker-channel-promote.mjs --find-statuses-file FILE --version YYYY.M.P --repository OWNER/REPO --source-sha SHA`,
"Usage: node scripts/docker-channel-promote.mjs --version YYYY.M.P --image REGISTRY/IMAGE [--image REGISTRY/IMAGE] [--image-tag-suffix -rYYYYMMDD] [--allow-rollback]",
);
}
@@ -397,10 +294,6 @@ function main() {
help: { type: "boolean", short: "h" },
image: { type: "string", multiple: true },
"image-tag-suffix": { type: "string", default: "" },
repository: { type: "string" },
"run-id": { type: "string" },
"source-sha": { type: "string" },
"status-payload": { type: "boolean" },
version: { type: "string" },
},
strict: true,
+2 -8
View File
@@ -669,10 +669,7 @@ describe("Docker channel promotion", () => {
"cancel-in-progress": false,
queue: "max",
});
expect(verifyAttestations.permissions).toEqual({
contents: "read",
packages: "write",
});
expect(verifyAttestations.permissions).toEqual({ contents: "read", packages: "write" });
const manifestTagStep = createManifest.steps?.find(
(step) => step.name === "Resolve manifest tags",
@@ -701,10 +698,7 @@ describe("Docker channel promotion", () => {
expect(releaseSteps[releasePromotionIndex]?.run).not.toContain("--allow-rollback");
expect(
Object.values(releaseWorkflow.jobs ?? {}).flatMap((job) =>
(job.steps ?? []).filter(
(step) =>
step.run?.includes("docker-channel-promote.mjs") && step.run.includes("--image"),
),
(job.steps ?? []).filter((step) => step.run?.includes("docker-channel-promote.mjs")),
),
).toHaveLength(1);