742f8f48b2
Build immutable API and web images in Aliyun ACR after staging validation, then deploy them safely to the staging host.
83 lines
2.8 KiB
JavaScript
83 lines
2.8 KiB
JavaScript
import { readFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
|
|
const shaPattern = /^[0-9a-f]{40}$/;
|
|
const digestPattern = /^sha256:[0-9a-f]{64}$/;
|
|
const expectedKeys = ["git_sha", "api_digest", "web_digest"];
|
|
const defaultRegistry = "ghcr.io/jesse-ux";
|
|
const acrRepository = "crpi-d1feco6itet73spp.cn-hongkong.personal.cr.aliyuncs.com/copse/jyotisha";
|
|
const registryPattern = /^(?:[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?)(?::[1-9][0-9]{0,4})?(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
|
|
|
|
export function parseStagingImageManifest(text, expectedSha, registry = defaultRegistry) {
|
|
if (!registryPattern.test(registry)) {
|
|
throw new Error("invalid staging image registry");
|
|
}
|
|
if (!shaPattern.test(expectedSha)) {
|
|
throw new Error("invalid expected staging revision");
|
|
}
|
|
|
|
const lines = text.endsWith("\n") ? text.slice(0, -1).split("\n") : text.split("\n");
|
|
if (lines.length !== expectedKeys.length) {
|
|
throw new Error("invalid staging image manifest");
|
|
}
|
|
|
|
const values = new Map();
|
|
for (const line of lines) {
|
|
const separator = line.indexOf("=");
|
|
if (separator <= 0) throw new Error("invalid staging image manifest");
|
|
const key = line.slice(0, separator);
|
|
const value = line.slice(separator + 1);
|
|
if (!expectedKeys.includes(key) || values.has(key)) {
|
|
throw new Error("invalid staging image manifest");
|
|
}
|
|
values.set(key, value);
|
|
}
|
|
|
|
if (values.get("git_sha") !== expectedSha) {
|
|
throw new Error("staging image manifest revision mismatch");
|
|
}
|
|
for (const key of ["api_digest", "web_digest"]) {
|
|
if (!digestPattern.test(values.get(key) ?? "")) {
|
|
throw new Error("invalid staging image digest");
|
|
}
|
|
}
|
|
|
|
const sharedRepository = registry === acrRepository;
|
|
return {
|
|
gitSha: expectedSha,
|
|
apiDigest: values.get("api_digest"),
|
|
webDigest: values.get("web_digest"),
|
|
apiImage: `${sharedRepository ? registry : `${registry}/jyotisha-api`}@${values.get("api_digest")}`,
|
|
webImage: `${sharedRepository ? registry : `${registry}/jyotisha-web`}@${values.get("web_digest")}`,
|
|
};
|
|
}
|
|
|
|
const invokedPath = process.argv[1]
|
|
? pathToFileURL(resolve(process.argv[1])).href
|
|
: undefined;
|
|
|
|
if (invokedPath === import.meta.url) {
|
|
try {
|
|
const [manifestPath, expectedSha, registry] = process.argv.slice(2);
|
|
if (!manifestPath || !expectedSha) {
|
|
throw new Error("manifest path and expected revision are required");
|
|
}
|
|
const manifest = parseStagingImageManifest(
|
|
await readFile(manifestPath, "utf8"),
|
|
expectedSha,
|
|
registry,
|
|
);
|
|
process.stdout.write(
|
|
[
|
|
`git_sha=${manifest.gitSha}`,
|
|
`api_image=${manifest.apiImage}`,
|
|
`web_image=${manifest.webImage}`,
|
|
].join("\n") + "\n",
|
|
);
|
|
} catch {
|
|
console.error("invalid staging image manifest");
|
|
process.exitCode = 1;
|
|
}
|
|
}
|