fix: skip v02 runtime wait on no-op promotion

This commit is contained in:
Codex
2026-05-30 09:41:31 +08:00
parent 77b4e8afa2
commit e2a29f9fd8
2 changed files with 112 additions and 2 deletions
+106 -2
View File
@@ -1274,6 +1274,8 @@ fs.writeFileSync("/workspace/source/affected-services.json", JSON.stringify({
buildServices: plan.buildServices || entries.filter((entry) => entry.buildRequired).map((entry) => entry.serviceId),
reusedServices: plan.reusedServices || [],
buildSkippedCount: plan.buildSkippedCount || 0,
changedPathSummary: plan.changedPathSummary || null,
ciCdPlan: plan.ciCdPlan || null,
services: plan.services || [],
entries
}, null, 2) + String.fromCharCode(10));
@@ -1408,6 +1410,7 @@ export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)"
export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)"
export HWLAB_TEKTON_TASK="gitops-promote"
export HWLAB_SOURCE_REVISION="$(params.revision)"
printf 'true' > /tekton/results/runtime-ready-required
echo '{"event":"ci-base-image","phase":"gitops-promote","image":"${ciToolsRunnerImage}","policy":"no-runtime-apt"}'
for tool in node git timeout; do command -v "$tool" >/dev/null 2>&1 || { echo '{"event":"ci-base-image","phase":"gitops-promote","ok":false,"reason":"missing-tool","tool":"'"$tool"'"}'; exit 31; }; done
${gitSshShellFunction()}
@@ -1558,9 +1561,14 @@ gitops_clone_started_ms="$(ci_now_ms)"
git_timed gitops-clone 180 git clone --no-checkout "$gitops_read_url" "$workdir/gitops"
cd "$workdir/gitops"
git remote set-url origin "$gitops_write_url"
old_runtime_snapshot="$workdir/old-runtime-snapshot"
if git_timed gitops-branch-ls 45 git ls-remote --exit-code --heads origin "$(params.gitops-branch)" >/dev/null; then
git_timed gitops-fetch 90 git fetch origin "$(params.gitops-branch)"
git checkout -B "$(params.gitops-branch)" "origin/$(params.gitops-branch)"
if [ "$lane" = "v02" ] && [ -d "$runtime_path" ]; then
mkdir -p "$old_runtime_snapshot"
cp -a "$runtime_path"/. "$old_runtime_snapshot"/
fi
if [ "$lane" = "v02" ]; then rm -rf "$runtime_path" "$catalog_path"; else rm -rf deploy/gitops/g14 "$catalog_path"; fi
else
git checkout --orphan "$(params.gitops-branch)"
@@ -1578,11 +1586,103 @@ else
cp "/workspace/source/repo/$catalog_path" "$catalog_path"
git add "$catalog_path" deploy/gitops/g14
fi
if [ "$lane" = "v02" ] && [ -s /workspace/source/affected-services.json ]; then
runtime_noop_decision="$(node - "$runtime_path" "$old_runtime_snapshot" /workspace/source/affected-services.json <<'NODE'
const fs = require("node:fs");
const path = require("node:path");
const [runtimePath, oldRuntimePath, planPath] = process.argv.slice(2);
function readJson(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function stable(value) {
if (Array.isArray(value)) return "[" + value.map(stable).join(",") + "]";
if (value && typeof value === "object") {
return "{" + Object.keys(value).sort().map((key) => JSON.stringify(key) + ":" + stable(value[key])).join(",") + "}";
}
return JSON.stringify(value);
}
function scrub(value) {
if (Array.isArray(value)) return value.map(scrub);
if (!value || typeof value !== "object") return value;
const result = {};
for (const [key, child] of Object.entries(value)) {
if (key === "hwlab.pikastech.local/source-commit" ||
key === "hwlab.pikastech.local/artifact-source-commit" ||
key === "hwlab.pikastech.local/boot-commit" ||
key === "sourceCommitId" ||
key === "bootCommit") {
result[key] = "<runtime-source-identity>";
continue;
}
if (key === "value" && /^HWLAB_.*COMMIT/.test(String(value.name || ""))) {
result[key] = "<runtime-source-identity>";
continue;
}
result[key] = scrub(child);
}
return result;
}
function listFiles(rootDir) {
if (!rootDir || !fs.existsSync(rootDir)) return null;
const files = [];
function walk(current) {
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
const fullPath = path.join(current, entry.name);
if (entry.isDirectory()) walk(fullPath);
else if (entry.isFile()) files.push(path.relative(rootDir, fullPath).replaceAll(path.sep, "/"));
}
}
walk(rootDir);
return files.sort();
}
function normalizedTree(rootDir) {
const files = listFiles(rootDir);
if (!files) return null;
const entries = {};
for (const relativePath of files) {
const filePath = path.join(rootDir, relativePath);
const text = fs.readFileSync(filePath, "utf8");
try {
entries[relativePath] = stable(scrub(JSON.parse(text)));
} catch {
entries[relativePath] = text.replace(/[a-f0-9]{40}/gu, "<runtime-source-identity>");
}
}
return stable(entries);
}
const plan = readJson(planPath);
const buildServices = Array.isArray(plan.buildServices) ? plan.buildServices : [];
const rolloutServices = Array.isArray(plan.rolloutServices) ? plan.rolloutServices : [];
if (buildServices.length > 0 || rolloutServices.length > 0) {
process.stdout.write("runtime-required");
process.exit(0);
}
const oldTree = normalizedTree(oldRuntimePath);
const newTree = normalizedTree(runtimePath);
process.stdout.write(oldTree && newTree && oldTree === newTree ? "runtime-identity-only" : "runtime-required");
NODE
)"
if [ "$runtime_noop_decision" = "runtime-identity-only" ]; then
printf 'false' > /tekton/results/runtime-ready-required
echo '{"status":"skipped-runtime-unchanged","reason":"runtime-identity-only","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
ci_timing_emit gitops-commit skipped "$(ci_now_ms)"
ci_timing_emit gitops-push skipped "$(ci_now_ms)"
exit 0
fi
fi
if git diff --cached --quiet; then
printf 'false' > /tekton/results/runtime-ready-required
echo '{"status":"unchanged","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
ci_timing_emit gitops-commit unchanged "$(ci_now_ms)"
ci_timing_emit gitops-push unchanged "$(ci_now_ms)"
argo_hard_refresh_v02
exit 0
fi
short="$(printf '%.7s' "$(params.revision)")"
@@ -2556,6 +2656,7 @@ function tektonPipeline(args = { lane: "g14" }) {
{ name: "revision" },
{ name: "registry-prefix" }
],
results: [{ name: "runtime-ready-required", description: "true when GitOps promotion changed runtime desired state and runtime readiness must be observed" }],
workspaces: [{ name: "source" }, { name: "git-ssh" }],
steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }]
},
@@ -2573,7 +2674,10 @@ function tektonPipeline(args = { lane: "g14" }) {
{ name: "registry-prefix", value: "$(params.registry-prefix)" }
]
},
runtimeReadyTask({ profile: settings.profile })
{
...runtimeReadyTask({ profile: settings.profile }),
when: [{ input: "$(tasks.gitops-promote.results.runtime-ready-required)", operator: "in", values: ["true"] }]
}
]
}
};
+6
View File
@@ -49,13 +49,19 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots
assert.match(pipeline, /item\.metadata\?\.labels\?\.\[\\"hwlab\.pikastech\.local\/source-commit\\"\]/u);
const runtimeReady = taskByName(pipelineJson, "runtime-ready");
assert.deepEqual(runtimeReady.when, [{ input: "$(tasks.gitops-promote.results.runtime-ready-required)", operator: "in", values: ["true"] }]);
const runtimeReadyScript = runtimeReady.taskSpec.steps[0].script;
assert.match(runtimeReadyScript, /const pollMs = 1000;/u);
assert.match(runtimeReadyScript, /status: "timeout", reason: "source-commit-refresh-timeout"/u);
assert.match(runtimeReadyScript, /const refresh = await waitForArgoRefresh\(\);\n if \(!refresh\.observed\) return;/u);
const gitopsPromote = taskByName(pipelineJson, "gitops-promote");
assert.ok(gitopsPromote.taskSpec.results.some((result) => result.name === "runtime-ready-required"));
const gitopsPromoteScript = gitopsPromote.taskSpec.steps[0].script;
assert.match(gitopsPromoteScript, /printf 'true' > \/tekton\/results\/runtime-ready-required/u);
assert.match(gitopsPromoteScript, /runtime-identity-only/u);
assert.match(gitopsPromoteScript, /skipped-runtime-unchanged/u);
assert.match(gitopsPromoteScript, /printf 'false' > \/tekton\/results\/runtime-ready-required/u);
assert.match(gitopsPromoteScript, /argo_hard_refresh_v02\(\) \{/u);
assert.match(gitopsPromoteScript, /argocd\.argoproj\.io\/refresh": "hard"/u);
assert.match(gitopsPromoteScript, /applications\/" \+ encodeURIComponent\(application\) \+ "\?fieldManager=hwlab-v02-gitops-promote/u);