95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
import { appendFile } from "node:fs/promises";
|
|
|
|
import {
|
|
cleanupFinalizedPromotion,
|
|
finalizeVerifiedPromotion,
|
|
} from "./promotion-stager.ts";
|
|
|
|
export async function runStageVerifiedPromotionCli(
|
|
environment: NodeJS.ProcessEnv,
|
|
dependencies: Readonly<{
|
|
cwd?: () => string;
|
|
finalize?: typeof finalizeVerifiedPromotion;
|
|
cleanup?: typeof cleanupFinalizedPromotion;
|
|
appendOutput?: (path: string, content: string) => Promise<void>;
|
|
writeStdout?: (content: string) => void;
|
|
}> = {},
|
|
): Promise<void> {
|
|
const required = (name: string): string => {
|
|
const value = environment[name];
|
|
if (!value) throw new TypeError(`promotion staging environment is missing ${name}`);
|
|
return value;
|
|
};
|
|
const attempt = Number(
|
|
environment.GITEA_RUN_ATTEMPT ??
|
|
environment.GITHUB_RUN_ATTEMPT ??
|
|
required("CI_RUN_ATTEMPT"),
|
|
);
|
|
if (!Number.isInteger(attempt) || attempt < 1 || attempt > 1_000) {
|
|
throw new TypeError("promotion staging run attempt is invalid");
|
|
}
|
|
const runnerTempRoot = required("RUNNER_TEMP");
|
|
const staged = await (dependencies.finalize ?? finalizeVerifiedPromotion)({
|
|
repositoryRoot: (dependencies.cwd ?? process.cwd)(),
|
|
archivePath: required("CANDIDATE_ARCHIVE_PATH"),
|
|
expectedArchiveSha256: required("CANDIDATE_ARCHIVE_SHA256"),
|
|
vulnerabilityReportPath: required("VULNERABILITY_REPORT_PATH"),
|
|
provenanceAttestationPath: required("PROVENANCE_ATTESTATION_PATH"),
|
|
vulnerabilityPublicKeyPath: required("VULNERABILITY_PUBLIC_KEY_PATH"),
|
|
vulnerabilityKeyId: required("VULNERABILITY_KEY_ID"),
|
|
provenancePublicKeyPath: required("PROVENANCE_PUBLIC_KEY_PATH"),
|
|
provenanceKeyId: required("PROVENANCE_KEY_ID"),
|
|
expectedRun: {
|
|
id:
|
|
environment.GITEA_RUN_ID ??
|
|
environment.GITHUB_RUN_ID ??
|
|
required("CI_RUN_ID"),
|
|
attempt,
|
|
sourceRevision:
|
|
environment.EXPECTED_SOURCE_REVISION ?? required("VITE_COMMIT_SHA"),
|
|
},
|
|
vulnerabilityInvocationNonce: required("VULNERABILITY_INVOCATION_NONCE"),
|
|
provenanceInvocationNonce: required("PROVENANCE_INVOCATION_NONCE"),
|
|
runnerTempRoot,
|
|
});
|
|
try {
|
|
const output = required("GITHUB_OUTPUT");
|
|
const content = [
|
|
`staging_root=${staged.stagingRoot}`,
|
|
`cleanup_token=${staged.cleanupToken}`,
|
|
`runner_temp_dev=${staged.runnerTempIdentity.dev}`,
|
|
`runner_temp_ino=${staged.runnerTempIdentity.ino}`,
|
|
`staging_dev=${staged.stagingIdentity.dev}`,
|
|
`staging_ino=${staged.stagingIdentity.ino}`,
|
|
"",
|
|
].join("\n");
|
|
await (dependencies.appendOutput ?? defaultAppendOutput)(output, content);
|
|
} catch (error) {
|
|
try {
|
|
await (dependencies.cleanup ?? cleanupFinalizedPromotion)({
|
|
runnerTempRoot,
|
|
stagingRoot: staged.stagingRoot,
|
|
cleanupToken: staged.cleanupToken,
|
|
runnerTempIdentity: staged.runnerTempIdentity,
|
|
stagingIdentity: staged.stagingIdentity,
|
|
});
|
|
} catch (cleanupError) {
|
|
throw new AggregateError(
|
|
[error, cleanupError],
|
|
"promotion output publication and direct staging cleanup both failed",
|
|
{ cause: cleanupError },
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
(dependencies.writeStdout ?? process.stdout.write.bind(process.stdout))(
|
|
`Promotion staging: ${staged.files
|
|
.map(({ name, sha256 }) => `${name}=${sha256}`)
|
|
.join(", ")} PASS\n`,
|
|
);
|
|
}
|
|
|
|
async function defaultAppendOutput(path: string, content: string): Promise<void> {
|
|
await appendFile(path, content, { encoding: "utf8" });
|
|
}
|