mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 03:15:46 -06:00
chore(release): drop obsolete Docker status remnants
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user