fix: skip v02 runtime wait on no-op promotion
This commit is contained in:
@@ -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"] }]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user