fix: apply deploy-json promotion in dev cd

This commit is contained in:
root
2026-05-23 11:34:16 +00:00
parent c75f0336d2
commit 345e4e225a
3 changed files with 316 additions and 9 deletions
+149 -5
View File
@@ -47,7 +47,8 @@ function parsePatch(args) {
return JSON.parse(args[index + 1]);
}
async function makeRepo() {
async function makeRepo(options = {}) {
const commitId = options.commitId ?? "abc1234";
const repoRoot = await fsTempDir();
await mkdir(path.join(repoRoot, "deploy"), { recursive: true });
await writeFile(
@@ -57,7 +58,7 @@ async function makeRepo() {
environment: "dev",
namespace: "hwlab-dev",
endpoint: "http://74.48.78.17:16667",
commitId: "abc1234"
commitId
}, null, 2)}\n`
);
return repoRoot;
@@ -98,7 +99,17 @@ function makeHttpGetJson() {
};
}
function makeRunCommand({ heldLock = null, commandLog = [] } = {}) {
const publishedCommit = "abc1234abc1234abc1234abc1234abc1234abc1";
const laterControlCommit = "def5678def5678def5678def5678def5678def5";
function makeRunCommand({
heldLock = null,
commandLog = [],
targetCommitId = publishedCommit,
headCommitId = targetCommitId,
extraGitRefs = {},
desiredStatePromotionStatus = "pass"
} = {}) {
let lease = heldLock ? leaseFromLock(heldLock) : null;
const assertLeaseMicroTime = (value) => {
assert.match(value, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$/u);
@@ -106,10 +117,16 @@ function makeRunCommand({ heldLock = null, commandLog = [] } = {}) {
return async (command, args, options = {}) => {
commandLog.push({ command, args, env: options.env ?? {}, input: options.input ?? "" });
if (command === "git" && args[0] === "rev-parse" && args.includes("origin/main^{commit}")) {
return { code: 0, stdout: "abc1234abc1234abc1234abc1234abc1234abc1\n", stderr: "" };
return { code: 0, stdout: `${targetCommitId}\n`, stderr: "" };
}
if (command === "git" && args[0] === "rev-parse" && args.includes("HEAD^{commit}")) {
return { code: 0, stdout: "abc1234abc1234abc1234abc1234abc1234abc1\n", stderr: "" };
return { code: 0, stdout: `${headCommitId}\n`, stderr: "" };
}
if (command === "git" && args[0] === "rev-parse" && args[1] === "--verify") {
const ref = String(args[2] ?? "").replace(/\^\{commit\}$/u, "");
const commitId = extraGitRefs[ref];
if (commitId) return { code: 0, stdout: `${commitId}\n`, stderr: "" };
return { code: 1, stdout: "", stderr: `fatal: ambiguous argument '${ref}'` };
}
if (command === "which" && args[0] === "kubectl") {
return { code: 0, stdout: "/usr/local/bin/kubectl\n", stderr: "" };
@@ -176,6 +193,30 @@ function makeRunCommand({ heldLock = null, commandLog = [] } = {}) {
lease.metadata.resourceVersion = String(Number(lease.metadata.resourceVersion ?? "2") + 1);
return { code: 0, stdout: `${JSON.stringify(lease)}\n`, stderr: "" };
}
if ((command === process.execPath || command.endsWith("/node")) && args.includes("scripts/deploy-desired-state-plan.mjs") && args.includes("--promotion-commit")) {
const promotionCommit = args[args.indexOf("--promotion-commit") + 1];
const status = desiredStatePromotionStatus;
return {
code: status === "pass" ? 0 : 1,
stdout: `${JSON.stringify({
status,
summary: {
desiredCommitId: promotionCommit.slice(0, 7),
artifactState: "published",
ciPublished: true,
registryVerified: true,
blockers: status === "pass" ? 0 : 1
},
target: {
convergence: {
state: status === "pass" ? "already_promoted" : "promotion_mismatch"
}
},
diagnostics: status === "pass" ? [] : [{ code: "promotion_commit_mismatch", path: "desired-state", message: "fixture mismatch" }]
})}\n`,
stderr: ""
};
}
if (command === process.execPath || command.endsWith("/node")) {
return { code: 0, stdout: JSON.stringify({ ok: true, command: args.join(" ") }), stderr: "" };
}
@@ -278,6 +319,48 @@ test("dev-cd default status is read-only compact JSON", async () => {
assertNoReadOnlySideEffects(commandLog);
});
test("dev-cd status accepts a published deploy-json promotion under a later control HEAD", async () => {
const repoRoot = await makeRepo({ commitId: "abc1234" });
const commandLog = [];
let output = "";
const code = await runDevCdApply(["--status", "--kubeconfig", "/tmp/kubeconfig"], {
repoRoot,
env: {},
runCommand: makeRunCommand({
commandLog,
targetCommitId: laterControlCommit,
headCommitId: laterControlCommit,
extraGitRefs: {
abc1234: publishedCommit
}
}),
httpGetJson: makeHttpGetJson(),
now: () => new Date(iso(100000)),
stdout: { write: (chunk) => { output += chunk; } }
});
const status = JSON.parse(output);
assert.equal(code, 0);
assert.equal(status.status, "pass");
assert.equal(status.target.promotionCommit, publishedCommit);
assert.equal(status.target.shortCommitId, "abc1234");
assert.equal(status.target.promotionSource, "deploy-json");
assert.equal(status.target.publishRequired, false);
assert.equal(status.target.controlRef.shortCommitId, "def5678");
assert.equal(status.target.headMatchesTarget, true);
assert.equal(status.target.desiredStateCheck.status, "pass");
assert.equal(status.deployJson.matchesTarget, true);
assert.match(status.nextActions[0], /without republishing HEAD/u);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs")), false);
assert.equal(commandLog.some((entry) =>
entry.args.includes("scripts/deploy-desired-state-plan.mjs") &&
entry.args.includes("--promotion-commit") &&
entry.args.includes(publishedCommit)
), true);
assertNoReadOnlySideEffects(commandLog);
});
test("dev-cd status summarizes held, released, and stale locks without unsafe takeover hints", async () => {
const cases = [
{
@@ -618,6 +701,67 @@ test("transaction runs phases, allows internal side-effect env, releases lock, a
assert.equal(writtenReport.devCdApply.liveVerify.summary.checked, 2);
});
test("transaction can apply an already published deploy-json promotion without republishing control HEAD", async () => {
const repoRoot = await makeRepo({ commitId: "abc1234" });
const commandLog = [];
let output = "";
const code = await runDevCdApply([
"--apply",
"--confirm-dev",
"--confirmed-non-production",
"--owner-task-id",
"task-promotion",
"--kubeconfig",
"/tmp/kubeconfig",
"--write-report",
"--full-output"
], {
repoRoot,
env: {},
runCommand: makeRunCommand({
commandLog,
targetCommitId: laterControlCommit,
headCommitId: laterControlCommit,
extraGitRefs: {
abc1234: publishedCommit
}
}),
httpGetJson: makeHttpGetJson(),
now: () => new Date(iso(100000)),
stdout: { write: (chunk) => { output += chunk; } }
});
const report = JSON.parse(output);
assert.equal(code, 0);
assert.equal(report.status, "pass");
assert.equal(report.devCdApply.target.promotionCommit, publishedCommit);
assert.equal(report.devCdApply.target.promotionSource, "deploy-json");
assert.equal(report.devCdApply.target.publishRequired, false);
assert.deepEqual(
report.devCdApply.steps.map((step) => step.id),
[
"desired-state-check-existing-promotion",
"artifact-catalog-validate",
"runtime-db-provisioning",
"runtime-db-migration",
"dev-deploy-apply",
"runtime-durable-postflight"
]
);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/dev-artifact-publish.mjs")), false);
assert.equal(commandLog.some((entry) => entry.args.includes("scripts/refresh-artifact-catalog.mjs")), false);
assert.equal(commandLog.some((entry) =>
entry.args.includes("scripts/deploy-desired-state-plan.mjs") &&
entry.args.includes("--promotion-commit") &&
entry.args.includes(publishedCommit)
), true);
const applyCall = commandLog.find((entry) => entry.args.includes("scripts/dev-deploy-apply.mjs"));
assert.ok(applyCall?.env.HWLAB_CD_TRANSACTION_ID);
assert.equal(applyCall.env.HWLAB_CD_TRANSACTION_OWNER, "task-promotion");
assert.equal(report.devCdApply.liveVerify.status, "pass");
assert.equal(report.devCdApply.liveVerify.summary.commitMatches, 2);
});
test("apply stdout is concise by default while write-report keeps the full report", async () => {
const repoRoot = await makeRepo();
let output = "";