fix: 校验 consumer 级发布计划信封
This commit is contained in:
@@ -10,6 +10,7 @@ const envIdentityExcludedPaths = new Set([
|
||||
"scripts/ci-plan.mjs",
|
||||
"scripts/ci-plan.test.mjs",
|
||||
"scripts/ci/verify-reviewed-plan.mjs",
|
||||
"scripts/ci/verify-reviewed-plan.test.mjs",
|
||||
]);
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
|
||||
@@ -21,6 +21,7 @@ test("plan reuses a present env image without expanding build scope", async () =
|
||||
await writeFile(join(root, "scripts", "ci-plan.mjs"), "planner-only\n");
|
||||
await writeFile(join(root, "scripts", "ci-plan.test.mjs"), "planner-test-only\n");
|
||||
await writeFile(join(root, "scripts", "ci", "verify-reviewed-plan.mjs"), "admission-only\n");
|
||||
await writeFile(join(root, "scripts", "ci", "verify-reviewed-plan.test.mjs"), "admission-test-only\n");
|
||||
assert.equal(calculateEnvIdentity({
|
||||
root,
|
||||
envIdentityFiles: ["src", "scripts"],
|
||||
|
||||
@@ -1,34 +1,43 @@
|
||||
#!/usr/bin/env node
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const [actualPath, envelopePath] = process.argv.slice(2);
|
||||
if (!actualPath || !envelopePath) throw new Error("usage: verify-reviewed-plan.mjs <actual-plan.json> <reviewed-plan-envelope>");
|
||||
const actual = JSON.parse(await readFile(actualPath, "utf8"));
|
||||
const envelope = (await readFile(envelopePath, "utf8")).trim();
|
||||
const prefix = "unidesk-release-plan-v1.";
|
||||
if (!envelope.startsWith(prefix)) throw new Error("reviewed plan envelope is missing or unsupported");
|
||||
const expected = JSON.parse(Buffer.from(envelope.slice(prefix.length), "base64url").toString("utf8"));
|
||||
const actualScope = scope(actual);
|
||||
const expectedScope = scope(expected);
|
||||
const additions = Object.fromEntries(Object.keys(actualScope).map((key) => [key, actualScope[key].filter((item) => !expectedScope[key].includes(item))]));
|
||||
const result = {
|
||||
event: "reviewed-plan-admission",
|
||||
ok: expected.version === "v1"
|
||||
&& expected.planIdentity === actual.planIdentity?.value
|
||||
&& expected.target === actual.planIdentity?.inputs?.target
|
||||
&& expected.consumer === actual.planIdentity?.inputs?.consumer
|
||||
&& expected.sourceCommit === actual.sourceCommitId
|
||||
&& expected.baseCommit === actual.baseCommitId
|
||||
&& Object.values(additions).every((items) => items.length === 0),
|
||||
expectedIdentity: expected.planIdentity ?? null,
|
||||
actualIdentity: actual.planIdentity?.value ?? null,
|
||||
expectedScope,
|
||||
actualScope,
|
||||
additions,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
process.stdout.write(`${JSON.stringify(result)}\n`);
|
||||
if (!result.ok) process.exit(42);
|
||||
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
const [actualPath, envelopePath] = process.argv.slice(2);
|
||||
if (!actualPath || !envelopePath) throw new Error("usage: verify-reviewed-plan.mjs <actual-plan.json> <reviewed-plan-envelope>");
|
||||
const actual = JSON.parse(await readFile(actualPath, "utf8"));
|
||||
const envelope = (await readFile(envelopePath, "utf8")).trim();
|
||||
const result = verifyReviewedPlan(actual, envelope);
|
||||
process.stdout.write(`${JSON.stringify(result)}\n`);
|
||||
if (!result.ok) process.exit(42);
|
||||
}
|
||||
|
||||
export function verifyReviewedPlan(actual, envelope) {
|
||||
const consumer = actual?.planIdentity?.inputs?.consumer;
|
||||
if (typeof consumer !== "string" || consumer.length === 0) throw new Error("actual plan consumer is missing");
|
||||
const prefix = `unidesk-release-plan-v1.${consumer}.`;
|
||||
if (!envelope.startsWith(prefix)) throw new Error("reviewed plan envelope is missing, unsupported, or belongs to another consumer");
|
||||
const expected = JSON.parse(Buffer.from(envelope.slice(prefix.length), "base64url").toString("utf8"));
|
||||
const actualScope = scope(actual);
|
||||
const expectedScope = scope(expected);
|
||||
const additions = Object.fromEntries(Object.keys(actualScope).map((key) => [key, actualScope[key].filter((item) => !expectedScope[key].includes(item))]));
|
||||
return {
|
||||
event: "reviewed-plan-admission",
|
||||
ok: expected.version === "v1"
|
||||
&& expected.planIdentity === actual.planIdentity?.value
|
||||
&& expected.target === actual.planIdentity?.inputs?.target
|
||||
&& expected.consumer === consumer
|
||||
&& expected.sourceCommit === actual.sourceCommitId
|
||||
&& expected.baseCommit === actual.baseCommitId
|
||||
&& Object.values(additions).every((items) => items.length === 0),
|
||||
expectedIdentity: expected.planIdentity ?? null,
|
||||
actualIdentity: actual.planIdentity?.value ?? null,
|
||||
expectedScope,
|
||||
actualScope,
|
||||
additions,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
function scope(value) {
|
||||
const sorted = (items) => [...new Set(Array.isArray(items) ? items.filter((item) => typeof item === "string" && item) : [])].sort();
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { verifyReviewedPlan } from "./verify-reviewed-plan.mjs";
|
||||
|
||||
const consumer = "agentrun-nc01-v02";
|
||||
const actual = {
|
||||
sourceCommitId: "a".repeat(40),
|
||||
baseCommitId: "b".repeat(40),
|
||||
buildServices: [],
|
||||
rolloutServices: ["agentrun-mgr"],
|
||||
affectedServices: ["agentrun-mgr"],
|
||||
planIdentity: {
|
||||
value: `sha256:${"c".repeat(64)}`,
|
||||
inputs: { target: "NC01", consumer },
|
||||
},
|
||||
};
|
||||
|
||||
function envelope(overrides = {}, envelopeConsumer = consumer) {
|
||||
const reviewed = {
|
||||
version: "v1",
|
||||
planIdentity: actual.planIdentity.value,
|
||||
target: "NC01",
|
||||
consumer,
|
||||
sourceCommit: actual.sourceCommitId,
|
||||
baseCommit: actual.baseCommitId,
|
||||
buildServices: [],
|
||||
rolloutServices: ["agentrun-mgr"],
|
||||
affectedServices: ["agentrun-mgr"],
|
||||
...overrides,
|
||||
};
|
||||
return `unidesk-release-plan-v1.${envelopeConsumer}.${Buffer.from(JSON.stringify(reviewed)).toString("base64url")}`;
|
||||
}
|
||||
|
||||
test("accepts the consumer-qualified reviewed plan envelope", () => {
|
||||
assert.equal(verifyReviewedPlan(actual, envelope()).ok, true);
|
||||
});
|
||||
|
||||
test("rejects an envelope addressed to another consumer", () => {
|
||||
assert.throws(() => verifyReviewedPlan(actual, envelope({}, "agentrun-nc01-release")), /another consumer/u);
|
||||
});
|
||||
|
||||
test("rejects actual scope that expands beyond the reviewed plan", () => {
|
||||
const expanded = { ...actual, buildServices: ["agentrun-mgr"] };
|
||||
const result = verifyReviewedPlan(expanded, envelope());
|
||||
assert.equal(result.ok, false);
|
||||
assert.deepEqual(result.additions.buildServices, ["agentrun-mgr"]);
|
||||
});
|
||||
Reference in New Issue
Block a user