From 5968a93a4bcaf73cfc431e1bc14a7e08633b63b8 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 10 Jul 2026 06:59:59 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=9D=9E=E8=BF=90=E8=A1=8C=E6=80=81?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E8=B7=B3=E8=BF=87=20Todo=20=E5=8F=91?= =?UTF-8?q?=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unidesk-cicd/references/gitea-pac.md | 10 +++ .tekton/unidesk-host-pac.yaml | 12 ++- config/unidesk-host-k8s.yaml | 16 ++++ .../native/cicd/build-unidesk-host-image.sh | 7 +- .../cicd/prepare-unidesk-host-release.mjs | 69 ++++++++++++++- .../cicd/publish-unidesk-host-gitops.mjs | 85 ++++++++++++++++--- ...platform-infra-pipelines-as-code-remote.sh | 22 ++++- 7 files changed, 200 insertions(+), 21 deletions(-) diff --git a/.agents/skills/unidesk-cicd/references/gitea-pac.md b/.agents/skills/unidesk-cicd/references/gitea-pac.md index 09034b06..a2acc7ad 100644 --- a/.agents/skills/unidesk-cicd/references/gitea-pac.md +++ b/.agents/skills/unidesk-cicd/references/gitea-pac.md @@ -57,4 +57,14 @@ For migrated sentinel CI/CD half-state triage, the PaC `status` command owns the - Sentinel `ENV_REUSE=hit` means dependency reuse was available; `cache=hit` is separate BuildKit evidence. - HWLAB `skipped,skip=` means service-level artifact planning skipped reusable builds; inspect the PipelineRun detail when deciding which service changed. +## UniDesk Host Runtime Change Filtering + +The `unidesk-host` consumer separates source observation from runtime delivery. `config/unidesk-host-k8s.yaml#delivery.changeDetection.runtimePaths` is the only editable list of source paths that can change the selected runtime, while `delivery.gitops.releaseStatePath` points to the generated GitOps release state used as the comparison baseline. + +- `build`: at least one declared runtime path changed, or no trustworthy prior release state is available. Build the image, update the manifest and release state, then let Argo converge. +- `skip`: the source commit advanced but no declared runtime path changed. Do not build an image, do not modify the GitOps branch, and do not cause an Argo rollout; retain the prior runtime source commit and digest as provenance. +- `disabled`: `delivery.enabled=false`. Remove the generated manifest and release state; this is service retirement, not a synonym for `skip`. + +CLI, documentation, issue metadata and other files outside `runtimePaths` must resolve to `IMAGE_STATUS=skipped`. A successful skip still has a short PaC PipelineRun as source-observation evidence, but image build and GitOps publication are skipped. `status` and `closeout` must validate the retained digest, GitOps revision, Argo state, runtime readiness and health without requiring the runtime source commit to equal the newer non-runtime source commit. + Missing reuse text is not proof of a failed reuse path. Check the consumer type and PipelineRun detail first. diff --git a/.tekton/unidesk-host-pac.yaml b/.tekton/unidesk-host-pac.yaml index fe8bcd36..ad032a8f 100644 --- a/.tekton/unidesk-host-pac.yaml +++ b/.tekton/unidesk-host-pac.yaml @@ -41,16 +41,23 @@ spec: env: - name: SOURCE_COMMIT value: "{{revision}}" + - name: SOURCE_SNAPSHOT_PREFIX + value: "{{source_snapshot_prefix}}" script: | #!/bin/sh set -eu rm -rf source release git clone --filter=blob:none --no-checkout http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-unidesk.git source cd source - git fetch --depth=1 --filter=blob:none origin "+refs/unidesk/snapshots/gitea-actions/unidesk-master-nc01/$SOURCE_COMMIT:refs/remotes/origin/unidesk-source-snapshot" + git fetch --depth=1 --filter=blob:none origin "+$SOURCE_SNAPSHOT_PREFIX/$SOURCE_COMMIT:refs/remotes/origin/unidesk-source-snapshot" git checkout --detach "$SOURCE_COMMIT" test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT" - bun scripts/native/cicd/prepare-unidesk-host-release.mjs --config config/unidesk-host-k8s.yaml --output-dir /workspace/release + bun scripts/native/cicd/prepare-unidesk-host-release.mjs \ + --config config/unidesk-host-k8s.yaml \ + --source-root /workspace/source \ + --source-commit "$SOURCE_COMMIT" \ + --source-snapshot-prefix "$SOURCE_SNAPSHOT_PREFIX" \ + --output-dir /workspace/release volumeMounts: - name: workspace mountPath: /workspace @@ -104,6 +111,7 @@ spec: --config /workspace/source/config/unidesk-host-k8s.yaml \ --source-root /workspace/source \ --metadata /workspace/build-metadata.json \ + --release-dir /workspace/release \ --source-commit "$SOURCE_COMMIT" \ --worktree /workspace/unidesk-host-gitops volumeMounts: diff --git a/config/unidesk-host-k8s.yaml b/config/unidesk-host-k8s.yaml index fd1fb88d..42638e68 100644 --- a/config/unidesk-host-k8s.yaml +++ b/config/unidesk-host-k8s.yaml @@ -20,6 +20,21 @@ runtime: delivery: enabled: true serviceRef: services.todoNote + changeDetection: + runtimePaths: + - .tekton/unidesk-host-pac.yaml + - config/platform-infra/pipelines-as-code.yaml + - config/unidesk-host-k8s.yaml + - scripts/native/cicd/build-unidesk-host-image.sh + - scripts/native/cicd/prepare-unidesk-host-release.mjs + - scripts/native/cicd/publish-unidesk-host-gitops.mjs + - scripts/native/deploy/render-unidesk-host-service.mjs + - src/components/microservices/todo-note/Dockerfile + - src/components/microservices/todo-note/bun.lock + - src/components/microservices/todo-note/package.json + - src/components/microservices/todo-note/src + - src/components/microservices/todo-note/tsconfig.json + - src/components/shared build: networkMode: host proxy: @@ -42,6 +57,7 @@ delivery: writeUrl: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git branch: unidesk-host-gitops manifestPath: deploy/gitops/unidesk-host/todo-note.yaml + releaseStatePath: deploy/gitops-state/unidesk-host/todo-note.json author: name: UniDesk Host CI email: unidesk-host-ci@unidesk.local diff --git a/scripts/native/cicd/build-unidesk-host-image.sh b/scripts/native/cicd/build-unidesk-host-image.sh index ca4499be..9b60dd0d 100755 --- a/scripts/native/cicd/build-unidesk-host-image.sh +++ b/scripts/native/cicd/build-unidesk-host-image.sh @@ -8,11 +8,12 @@ metadata_file=${UNIDESK_HOST_BUILD_METADATA_FILE:-/workspace/build-metadata.json build_log=${UNIDESK_HOST_BUILD_LOG:-/workspace/image-build.log} source_commit=${SOURCE_COMMIT:?SOURCE_COMMIT is required} -enabled=$(cat "$release_dir/enabled") -if [ "$enabled" != true ]; then +action=$(cat "$release_dir/action") +reason=$(cat "$release_dir/reason") +if [ "$action" != build ]; then : >"$digest_file" : >"$metadata_file" - printf '{"ok":true,"phase":"image-build","status":"skipped","reason":"delivery-disabled","sourceCommit":"%s","valuesPrinted":false}\n' "$source_commit" + printf '{"ok":true,"phase":"image-build","status":"skipped","action":"%s","reason":"%s","sourceCommit":"%s","valuesPrinted":false}\n' "$action" "$reason" "$source_commit" exit 0 fi diff --git a/scripts/native/cicd/prepare-unidesk-host-release.mjs b/scripts/native/cicd/prepare-unidesk-host-release.mjs index ebcca4fe..20c02d57 100755 --- a/scripts/native/cicd/prepare-unidesk-host-release.mjs +++ b/scripts/native/cicd/prepare-unidesk-host-release.mjs @@ -1,4 +1,5 @@ #!/usr/bin/env bun +import { spawnSync } from "node:child_process"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; @@ -20,6 +21,27 @@ function required(value, path) { return value; } +function safeRelativePath(value, path) { + const result = required(value, path); + if (result.startsWith("/") || result.split("/").includes("..")) throw new Error(`${path} must be a safe relative path`); + return result; +} + +function stringArray(value, path) { + if (!Array.isArray(value) || value.length === 0) throw new Error(`${path} must be a non-empty string array`); + return value.map((item, index) => safeRelativePath(item, `${path}[${index}]`)); +} + +function run(command, args, cwd, allowFailure = false) { + const result = spawnSync(command, args, { cwd, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 }); + if (result.error !== undefined) throw result.error; + if (result.status !== 0 && !allowFailure) { + const detail = `${result.stderr || result.stdout || "command failed"}`.trim().slice(-2000); + throw new Error(`${command} ${args.join(" ")} failed (${result.status}): ${detail}`); + } + return result; +} + function valueAt(root, reference) { return reference.split(".").reduce((value, key) => record(value, reference)[key], root); } @@ -30,20 +52,65 @@ function writeValue(outputDir, name, value) { function main() { const configPath = option("--config") ?? "config/unidesk-host-k8s.yaml"; +const sourceRoot = resolve(option("--source-root") ?? process.cwd()); +const sourceCommit = required(option("--source-commit"), "--source-commit"); +const sourceSnapshotPrefix = required(option("--source-snapshot-prefix"), "--source-snapshot-prefix"); const outputDir = resolve(required(option("--output-dir"), "--output-dir")); +if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error("--source-commit must be a full Git commit SHA"); +if (run("git", ["check-ref-format", sourceSnapshotPrefix], sourceRoot, true).status !== 0) throw new Error("--source-snapshot-prefix must be a valid Git ref prefix"); const config = record(Bun.YAML.parse(readFileSync(configPath, "utf8")), configPath); const delivery = record(config.delivery, "delivery"); const serviceRef = required(delivery.serviceRef, "delivery.serviceRef"); const service = record(valueAt(config, serviceRef), serviceRef); +const changeDetection = record(delivery.changeDetection, "delivery.changeDetection"); +const runtimePaths = stringArray(changeDetection.runtimePaths, "delivery.changeDetection.runtimePaths"); const build = record(delivery.build, "delivery.build"); const proxy = record(build.proxy, "delivery.build.proxy"); const image = record(delivery.image, "delivery.image"); +const gitops = record(delivery.gitops, "delivery.gitops"); +const readUrl = required(gitops.readUrl, "delivery.gitops.readUrl"); +const branch = required(gitops.branch, "delivery.gitops.branch"); +const releaseStatePath = safeRelativePath(gitops.releaseStatePath, "delivery.gitops.releaseStatePath"); const enabled = delivery.enabled === true; if (delivery.enabled !== true && delivery.enabled !== false) throw new Error("delivery.enabled must be boolean"); const noProxy = proxy.noProxy; if (!Array.isArray(noProxy) || noProxy.some((value) => typeof value !== "string" || value.length === 0)) throw new Error("delivery.build.proxy.noProxy must be a non-empty string array"); +let action = enabled ? "build" : "disabled"; +let reason = enabled ? "gitops-baseline-unavailable" : "delivery-disabled"; +let baselineSourceCommit = null; +let changedPaths = []; +if (enabled) { + const gitopsRef = "refs/unidesk/release-baseline/unidesk-host"; + const gitopsFetch = run("git", ["fetch", "--depth=1", readUrl, `+refs/heads/${branch}:${gitopsRef}`], sourceRoot, true); + if (gitopsFetch.status === 0) { + const stateResult = run("git", ["show", `${gitopsRef}:${releaseStatePath}`], sourceRoot, true); + if (stateResult.status === 0) { + const state = record(JSON.parse(stateResult.stdout), releaseStatePath); + if (state.kind !== "UniDeskHostReleaseState") throw new Error(`${releaseStatePath}.kind must be UniDeskHostReleaseState`); + if (state.serviceRef !== serviceRef) throw new Error(`${releaseStatePath}.serviceRef must match delivery.serviceRef`); + baselineSourceCommit = required(state.sourceCommit, `${releaseStatePath}.sourceCommit`); + if (!/^[0-9a-f]{40}$/u.test(baselineSourceCommit)) throw new Error(`${releaseStatePath}.sourceCommit must be a full Git commit SHA`); + const sourceRef = "refs/unidesk/release-baseline/unidesk-host-source"; + const sourceFetch = run("git", ["fetch", "--depth=1", "--filter=blob:none", "origin", `+${sourceSnapshotPrefix}/${baselineSourceCommit}:${sourceRef}`], sourceRoot, true); + if (sourceFetch.status === 0) { + const diff = run("git", ["diff", "--name-only", "--no-renames", sourceRef, sourceCommit, "--", ...runtimePaths], sourceRoot); + changedPaths = diff.stdout.split(/\r?\n/u).filter(Boolean); + action = changedPaths.length === 0 ? "skip" : "build"; + reason = changedPaths.length === 0 ? "runtime-inputs-unchanged" : "runtime-inputs-changed"; + } else { + reason = "source-baseline-unavailable"; + } + } else { + reason = "release-state-missing"; + } + } +} mkdirSync(outputDir, { recursive: true }); writeValue(outputDir, "enabled", enabled ? "true" : "false"); +writeValue(outputDir, "action", action); +writeValue(outputDir, "reason", reason); +writeValue(outputDir, "baseline-source-commit", baselineSourceCommit ?? ""); +writeFileSync(resolve(outputDir, "changed-paths.json"), `${JSON.stringify(changedPaths)}\n`, { encoding: "utf8", mode: 0o644 }); writeValue(outputDir, "dockerfile", required(record(service.repository, `${serviceRef}.repository`).dockerfile, `${serviceRef}.repository.dockerfile`)); writeValue(outputDir, "image-repository", required(image.repository, "delivery.image.repository")); writeValue(outputDir, "network-mode", required(build.networkMode, "delivery.build.networkMode")); @@ -51,7 +118,7 @@ writeValue(outputDir, "http-proxy", required(proxy.http, "delivery.build.proxy.h writeValue(outputDir, "https-proxy", required(proxy.https, "delivery.build.proxy.https")); writeValue(outputDir, "all-proxy", required(proxy.all, "delivery.build.proxy.all")); writeValue(outputDir, "no-proxy", noProxy.join(",")); -process.stdout.write(`${JSON.stringify({ ok: true, action: "unidesk-host-release-prepare", enabled, serviceRef, valuesPrinted: false })}\n`); +process.stdout.write(`${JSON.stringify({ ok: true, phase: "release-prepare", action, reason, enabled, serviceRef, sourceCommit, baselineSourceCommit, changedPaths, valuesPrinted: false })}\n`); } if (!process.execArgv.includes("--check")) main(); diff --git a/scripts/native/cicd/publish-unidesk-host-gitops.mjs b/scripts/native/cicd/publish-unidesk-host-gitops.mjs index 17e75f64..018a83d3 100755 --- a/scripts/native/cicd/publish-unidesk-host-gitops.mjs +++ b/scripts/native/cicd/publish-unidesk-host-gitops.mjs @@ -37,10 +37,47 @@ function safeManifestPath(value) { return path; } +function safeReleaseStatePath(value) { + const path = required(value, "delivery.gitops.releaseStatePath"); + if (path.startsWith("/") || path.split("/").includes("..")) throw new Error("delivery.gitops.releaseStatePath must be a safe relative path"); + return path; +} + +function releaseValue(releaseDir, name) { + return required(readFileSync(resolve(releaseDir, name), "utf8").trim(), `${releaseDir}/${name}`); +} + +function removeFile(path) { + try { + unlinkSync(path); + } catch (error) { + if (error?.code !== "ENOENT") throw error; + } +} + +function readReleaseState(path, serviceRef) { + let value; + try { + value = record(JSON.parse(readFileSync(path, "utf8")), path); + } catch (error) { + if (error?.code === "ENOENT") return null; + throw error; + } + if (value.kind !== "UniDeskHostReleaseState") throw new Error(`${path}.kind must be UniDeskHostReleaseState`); + if (value.serviceRef !== serviceRef) throw new Error(`${path}.serviceRef must match delivery.serviceRef`); + const sourceCommit = required(value.sourceCommit, `${path}.sourceCommit`); + const digest = required(value.digest, `${path}.digest`); + const digestRef = required(value.digestRef, `${path}.digestRef`); + if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error(`${path}.sourceCommit must be a full Git commit SHA`); + if (!/^sha256:[0-9a-f]{64}$/u.test(digest)) throw new Error(`${path}.digest must be sha256:<64 hex>`); + return { sourceCommit, digest, digestRef }; +} + function main() { const configPath = resolve(option("--config") ?? "config/unidesk-host-k8s.yaml"); const sourceRoot = resolve(option("--source-root") ?? process.cwd()); const metadataPath = resolve(required(option("--metadata"), "--metadata")); +const releaseDir = resolve(required(option("--release-dir"), "--release-dir")); const sourceCommit = required(option("--source-commit"), "--source-commit"); const worktree = resolve(option("--worktree") ?? "/workspace/unidesk-host-gitops"); if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error("--source-commit must be a full Git commit SHA"); @@ -49,6 +86,11 @@ const config = record(Bun.YAML.parse(readFileSync(configPath, "utf8")), configPa const delivery = record(config.delivery, "delivery"); if (delivery.enabled !== true && delivery.enabled !== false) throw new Error("delivery.enabled must be boolean"); const enabled = delivery.enabled === true; +const action = releaseValue(releaseDir, "action"); +const reason = releaseValue(releaseDir, "reason"); +const baselineSourceCommit = readFileSync(resolve(releaseDir, "baseline-source-commit"), "utf8").trim() || null; +if (!new Set(["build", "skip", "disabled"]).has(action)) throw new Error(`${releaseDir}/action must be build, skip, or disabled`); +if ((enabled && action === "disabled") || (!enabled && action !== "disabled")) throw new Error("release action does not match delivery.enabled"); const serviceRef = required(delivery.serviceRef, "delivery.serviceRef"); const imageConfig = record(delivery.image, "delivery.image"); const gitops = record(delivery.gitops, "delivery.gitops"); @@ -57,15 +99,18 @@ const readUrl = required(gitops.readUrl, "delivery.gitops.readUrl"); const writeUrl = required(gitops.writeUrl, "delivery.gitops.writeUrl"); const branch = required(gitops.branch, "delivery.gitops.branch"); const manifestPath = safeManifestPath(gitops.manifestPath); +const releaseStatePath = safeReleaseStatePath(gitops.releaseStatePath); let digest = null; let digestRef = null; let manifest = null; +let runtimeSourceCommit = null; -if (enabled) { +if (action === "build") { const metadata = record(JSON.parse(readFileSync(metadataPath, "utf8")), metadataPath); digest = required(metadata["containerimage.digest"], `${metadataPath}.containerimage.digest`); if (!/^sha256:[0-9a-f]{64}$/u.test(digest)) throw new Error("containerimage.digest must be sha256:<64 hex>"); digestRef = `${required(imageConfig.repository, "delivery.image.repository")}@${digest}`; + runtimeSourceCommit = sourceCommit; const render = run("bun", [ "scripts/native/deploy/render-unidesk-host-service.mjs", "--config", @@ -92,16 +137,30 @@ if (fetched) { } const targetPath = resolve(worktree, manifestPath); +const statePath = resolve(worktree, releaseStatePath); if (!targetPath.startsWith(`${worktree}/`)) throw new Error("resolved manifest path escaped the GitOps worktree"); -if (enabled && manifest !== null) { +if (!statePath.startsWith(`${worktree}/`)) throw new Error("resolved release state path escaped the GitOps worktree"); +const existingState = readReleaseState(statePath, serviceRef); +if (action === "skip") { + if (existingState === null) throw new Error("skip requires an existing GitOps release state"); + digest = existingState.digest; + digestRef = existingState.digestRef; + runtimeSourceCommit = existingState.sourceCommit; +} else if (action === "build" && manifest !== null) { mkdirSync(dirname(targetPath), { recursive: true }); writeFileSync(targetPath, manifest, "utf8"); + mkdirSync(dirname(statePath), { recursive: true }); + writeFileSync(statePath, `${JSON.stringify({ + version: 1, + kind: "UniDeskHostReleaseState", + serviceRef, + sourceCommit, + digest, + digestRef, + }, null, 2)}\n`, "utf8"); } else { - try { - unlinkSync(targetPath); - } catch (error) { - if (error?.code !== "ENOENT") throw error; - } + removeFile(targetPath); + removeFile(statePath); } run("git", ["config", "user.name", required(author.name, "delivery.gitops.author.name")], worktree); @@ -109,8 +168,8 @@ run("git", ["config", "user.email", required(author.email, "delivery.gitops.auth run("git", ["add", "-A"], worktree); const changed = run("git", ["diff", "--cached", "--quiet"], worktree, true).status !== 0; if (changed) { - const action = enabled ? "deploy" : "remove"; - run("git", ["commit", "-m", `unidesk-host: ${action} ${serviceRef} ${sourceCommit.slice(0, 12)}`], worktree); + const commitAction = action === "build" ? "deploy" : "remove"; + run("git", ["commit", "-m", `unidesk-host: ${commitAction} ${serviceRef} ${sourceCommit.slice(0, 12)}`], worktree); } run("git", ["remote", "set-url", "origin", writeUrl], worktree); @@ -139,9 +198,13 @@ const gitopsCommit = head.status === 0 ? head.stdout.trim() : null; process.stdout.write(`${JSON.stringify({ ok: true, phase: "gitops-publish", - status: enabled ? (changed ? "built" : "reused") : "disabled", - imageStatus: enabled ? (changed ? "built" : "reused") : "disabled", + action, + reason, + status: action === "build" ? "built" : action === "skip" ? "skipped" : "disabled", + imageStatus: action === "build" ? "built" : action === "skip" ? "skipped" : "disabled", sourceCommit, + baselineSourceCommit, + runtimeSourceCommit, digest, digestRef, gitopsCommit, diff --git a/scripts/src/platform-infra-pipelines-as-code-remote.sh b/scripts/src/platform-infra-pipelines-as-code-remote.sh index a4f58e0b..7e2166c8 100644 --- a/scripts/src/platform-infra-pipelines-as-code-remote.sh +++ b/scripts/src/platform-infra-pipelines-as-code-remote.sh @@ -727,6 +727,10 @@ if (image) { digests: loggedDigests, gitopsCommit, sourceCommit: image.sourceCommit || null, + runtimeSourceCommit: image.runtimeSourceCommit || null, + baselineSourceCommit: image.baselineSourceCommit || null, + action: image.action || null, + reason: image.reason || null, valuesPrinted: false, }; } else if (humanEnv || gitopsCommit || loggedDigests.length > 0) { @@ -930,13 +934,15 @@ cicd_diagnostics() { printf '%s' "${2:-{}}" >"$artifact_file" printf '%s' "${3:-{}}" >"$argo_file" printf '%s' "${4:-{}}" >"$runtime_file" - probe_env=$(node - "$params_file" "$pipelines_file" <<'NODE' + probe_env=$(node - "$params_file" "$pipelines_file" "$artifact_file" <<'NODE' const fs = require('node:fs'); const params = JSON.parse(fs.readFileSync(process.argv[2], 'utf8') || '{}'); const pipelines = JSON.parse(fs.readFileSync(process.argv[3], 'utf8') || '[]'); +const artifact = JSON.parse(fs.readFileSync(process.argv[4], 'utf8') || '{}'); const latest = Array.isArray(pipelines) ? pipelines[0] || {} : {}; const sourceCommit = latest.sourceCommit || null; -const tag = sourceCommit ? String(sourceCommit).slice(0, 12) : ''; +const runtimeSourceCommit = artifact.runtimeSourceCommit || sourceCommit; +const tag = runtimeSourceCommit ? String(runtimeSourceCommit).slice(0, 12) : ''; const prefix = process.env.UNIDESK_PAC_CONSUMER_ID === 'unidesk-host' ? 'unidesk_host_' : ''; const param = (name) => params[`${prefix}${name}`] ?? params[name]; const imageRepository = typeof param('image_repository') === 'string' ? param('image_repository') : ''; @@ -955,6 +961,7 @@ function line(key, value) { line('imageRepository', imageRepository); line('registryProbeBase', probeBase); line('sourceCommit', sourceCommit || ''); +line('runtimeSourceCommit', runtimeSourceCommit || ''); line('tag', tag); line('registryUrl', registryUrl); line('sentinelId', param('sentinel_id') || ''); @@ -970,6 +977,7 @@ NODE image_repository=$(printf '%s\n' "$probe_env" | sed -n 's/^imageRepository=//p' | base64 -d 2>/dev/null || true) registry_probe_base=$(printf '%s\n' "$probe_env" | sed -n 's/^registryProbeBase=//p' | base64 -d 2>/dev/null || true) source_commit=$(printf '%s\n' "$probe_env" | sed -n 's/^sourceCommit=//p' | base64 -d 2>/dev/null || true) + runtime_source_commit=$(printf '%s\n' "$probe_env" | sed -n 's/^runtimeSourceCommit=//p' | base64 -d 2>/dev/null || true) image_tag=$(printf '%s\n' "$probe_env" | sed -n 's/^tag=//p' | base64 -d 2>/dev/null || true) registry_url=$(printf '%s\n' "$probe_env" | sed -n 's/^registryUrl=//p' | base64 -d 2>/dev/null || true) sentinel_id=$(printf '%s\n' "$probe_env" | sed -n 's/^sentinelId=//p' | base64 -d 2>/dev/null || true) @@ -1017,6 +1025,7 @@ NODE export UNIDESK_PAC_DIAG_IMAGE_REPOSITORY="$image_repository" export UNIDESK_PAC_DIAG_REGISTRY_PROBE_BASE="$registry_probe_base" export UNIDESK_PAC_DIAG_SOURCE_COMMIT="$source_commit" + export UNIDESK_PAC_DIAG_RUNTIME_SOURCE_COMMIT="$runtime_source_commit" export UNIDESK_PAC_DIAG_IMAGE_TAG="$image_tag" export UNIDESK_PAC_DIAG_REGISTRY_URL="$registry_url" export UNIDESK_PAC_DIAG_REGISTRY_PRESENT="$registry_present_json" @@ -1062,6 +1071,7 @@ const gitopsRelation = revisionRelation.relation || 'unknown'; const gitopsReady = Boolean(artifact.gitopsCommit && argo.revision); const gitopsRevisionAligned = gitopsRelation === 'exact' || gitopsRelation === 'descendant'; const deliveryDisabled = artifact.imageStatus === 'disabled'; +const deliverySkipped = artifact.imageStatus === 'skipped'; let code = 'pac-diagnostics-not-applicable'; let phase = 'not-applicable'; let ok = true; @@ -1089,9 +1099,11 @@ if (deliveryDisabled) { } else if (!healthReady) { ok = false; code = 'pac-health-not-ready'; phase = 'runtime-ready-health-pending'; hint = 'runtime image is aligned but the configured health endpoint is not ready'; } else if (artifactDigest || selectedArtifactDigests.length > 0 || process.env.UNIDESK_PAC_DIAG_IMAGE_REPOSITORY) { - code = gitopsRelation === 'descendant' ? 'pac-ready-gitops-descendant' : 'pac-ready-gitops-exact'; + code = deliverySkipped ? 'pac-ready-runtime-unchanged' : gitopsRelation === 'descendant' ? 'pac-ready-gitops-descendant' : 'pac-ready-gitops-exact'; phase = 'ready'; - hint = gitopsRelation === 'descendant' + hint = deliverySkipped + ? 'YAML-declared runtime inputs are unchanged; the existing image, GitOps manifest, runtime and health remain aligned' + : gitopsRelation === 'descendant' ? 'Argo is on a commit-graph-proven GitOps descendant and the selected artifact, runtime, Argo and health are aligned' : 'Argo is on the exact selected GitOps commit and the artifact, runtime and health are aligned'; } @@ -1101,6 +1113,7 @@ process.stdout.write(JSON.stringify({ phase, hint, sourceCommit, + runtimeSourceCommit: process.env.UNIDESK_PAC_DIAG_RUNTIME_SOURCE_COMMIT || artifact.runtimeSourceCommit || null, imageRepository: process.env.UNIDESK_PAC_DIAG_IMAGE_REPOSITORY || null, registryProbeBase: process.env.UNIDESK_PAC_DIAG_REGISTRY_PROBE_BASE || null, imageTag: process.env.UNIDESK_PAC_DIAG_IMAGE_TAG || null, @@ -1128,6 +1141,7 @@ process.stdout.write(JSON.stringify({ health: { url: healthUrl, status: healthStatus, ready: healthReady }, pipelineRun: latest.name || null, deliveryDisabled, + deliverySkipped, valuesPrinted: false, })); NODE