fix: expose pac sentinel cicd diagnosis
Pipelines as Code CI / hwlab-web-probe-sentinel-jd01- Success

This commit is contained in:
Codex
2026-07-06 16:56:48 +00:00
parent def6a2c768
commit bd8e844af6
7 changed files with 290 additions and 28 deletions
@@ -27,6 +27,23 @@ duration_seconds() {
echo null
}
json_normalize() {
UNIDESK_JSON_NORMALIZE_INPUT="${1:-}" node <<'NODE'
const input = String(process.env.UNIDESK_JSON_NORMALIZE_INPUT || '').trim();
if (!input) {
process.stdout.write('{}');
process.exit(0);
}
for (let end = input.length; end > 0; end -= 1) {
try {
process.stdout.write(JSON.stringify(JSON.parse(input.slice(0, end))));
process.exit(0);
} catch {}
}
process.stdout.write('{}');
NODE
}
api_url() {
prepare_gitea_api_base
printf '%s/api/v1/%s' "$UNIDESK_PAC_GITEA_API_BASE_URL" "$1"
@@ -632,29 +649,35 @@ function envReuseOf(item) {
const env = item.envReuse || {};
return env.dependencyReuse || env.mode || item.envReuseStatus || null;
}
process.stdout.write(JSON.stringify(image ? {
imageStatus: image.imageStatus || image.status || (image.digestRef ? 'built' : null),
envIdentity: image.envIdentity || null,
envReuse: envReuseOf(image),
nodeDepsReuse: null,
buildCache: null,
digest: digestOf(image),
gitopsCommit: image.gitopsCommit || null,
sourceCommit: image.sourceCommit || null,
valuesPrinted: false,
} : humanEnv ? {
imageStatus: 'published',
envIdentity: null,
envReuse: humanEnv.envReuse,
nodeDepsReuse: humanEnv.nodeDeps,
buildCache: humanEnv.cache,
buildPackage: humanEnv.buildPackage,
buildNetwork: humanEnv.buildNetwork,
digest: null,
gitopsCommit: null,
sourceCommit: null,
valuesPrinted: false,
} : { valuesPrinted: false }));
let out = { valuesPrinted: false };
if (image) {
out = {
imageStatus: image.imageStatus || image.status || (image.digestRef ? 'built' : null),
envIdentity: image.envIdentity || null,
envReuse: envReuseOf(image),
nodeDepsReuse: null,
buildCache: null,
digest: digestOf(image),
gitopsCommit: image.gitopsCommit || null,
sourceCommit: image.sourceCommit || null,
valuesPrinted: false,
};
} else if (humanEnv) {
out = {
imageStatus: 'published',
envIdentity: null,
envReuse: humanEnv.envReuse,
nodeDepsReuse: humanEnv.nodeDeps,
buildCache: humanEnv.cache,
buildPackage: humanEnv.buildPackage,
buildNetwork: humanEnv.buildNetwork,
digest: null,
gitopsCommit: null,
sourceCommit: null,
valuesPrinted: false,
};
}
process.stdout.write(JSON.stringify(out));
NODE
rm -f "$log_file"
}
@@ -701,6 +724,149 @@ NODE
rm -f "$deploy_file"
}
cicd_diagnostics() {
params_file=$(mktemp)
pipelines_file=$(mktemp)
artifact_file=$(mktemp)
argo_file=$(mktemp)
runtime_file=$(mktemp)
printf '%s' "$UNIDESK_PAC_PARAMS_JSON" >"$params_file"
printf '%s' "${1:-[]}" >"$pipelines_file"
printf '%s' "${2:-{}}" >"$artifact_file"
printf '%s' "${3:-{}}" >"$argo_file"
printf '%s' "${4:-{}}" >"$runtime_file"
probe_env=$(node - "$params_file" "$pipelines_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 latest = Array.isArray(pipelines) ? pipelines[0] || {} : {};
const sourceCommit = latest.sourceCommit || null;
const tag = sourceCommit ? String(sourceCommit).slice(0, 12) : '';
const imageRepository = typeof params.image_repository === 'string' ? params.image_repository : '';
const probeBase = typeof params.registry_probe_base === 'string' ? params.registry_probe_base.replace(/\/+$/u, '') : '';
let registryUrl = '';
if (imageRepository && tag) {
const firstSlash = imageRepository.indexOf('/');
const registry = firstSlash >= 0 ? imageRepository.slice(0, firstSlash) : imageRepository;
const path = firstSlash >= 0 ? imageRepository.slice(firstSlash + 1) : '';
const base = probeBase || (registry ? `http://${registry}` : '');
registryUrl = base && path ? `${base}/v2/${path}/manifests/${tag}` : '';
}
function line(key, value) {
process.stdout.write(`${key}=${Buffer.from(String(value || ''), 'utf8').toString('base64')}\n`);
}
line('imageRepository', imageRepository);
line('registryProbeBase', probeBase);
line('sourceCommit', sourceCommit || '');
line('tag', tag);
line('registryUrl', registryUrl);
line('sentinelId', params.sentinel_id || '');
line('gitopsBranch', params.gitops_branch || '');
line('gitopsManifestPath', params.gitops_manifest_path || '');
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)
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)
gitops_branch=$(printf '%s\n' "$probe_env" | sed -n 's/^gitopsBranch=//p' | base64 -d 2>/dev/null || true)
gitops_manifest_path=$(printf '%s\n' "$probe_env" | sed -n 's/^gitopsManifestPath=//p' | base64 -d 2>/dev/null || true)
registry_present=false
registry_digest=""
if [ -n "$registry_url" ]; then
header_file=$(mktemp)
if curl -fsS -D "$header_file" -o /dev/null -H 'Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' "$registry_url" >/dev/null 2>&1; then
registry_present=true
fi
headers=$(cat "$header_file" 2>/dev/null || true)
rm -f "$header_file"
if printf '%s' "$headers" | grep -qi '^HTTP/.* 200'; then registry_present=true; fi
registry_digest=$(printf '%s\n' "$headers" | awk 'BEGIN{IGNORECASE=1} /^Docker-Content-Digest:/ { sub(/\r$/,"",$2); print $2; exit }')
fi
registry_present_json=false
if [ "$registry_present" = true ]; then registry_present_json=true; fi
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_IMAGE_TAG="$image_tag"
export UNIDESK_PAC_DIAG_REGISTRY_URL="$registry_url"
export UNIDESK_PAC_DIAG_REGISTRY_PRESENT="$registry_present_json"
export UNIDESK_PAC_DIAG_REGISTRY_DIGEST="$registry_digest"
export UNIDESK_PAC_DIAG_SENTINEL_ID="$sentinel_id"
export UNIDESK_PAC_DIAG_GITOPS_BRANCH="$gitops_branch"
export UNIDESK_PAC_DIAG_GITOPS_MANIFEST_PATH="$gitops_manifest_path"
node - "$params_file" "$pipelines_file" "$artifact_file" "$argo_file" "$runtime_file" <<'NODE'
const fs = require('node:fs');
function parseLoose(path, fallback) {
const input = fs.readFileSync(path, 'utf8').trim();
if (!input) return fallback;
for (let end = input.length; end > 0; end -= 1) {
try { return JSON.parse(input.slice(0, end)); } catch {}
}
return fallback;
}
const params = parseLoose(process.argv[2], {});
const pipelines = parseLoose(process.argv[3], []);
const artifact = parseLoose(process.argv[4], {});
const argo = parseLoose(process.argv[5], {});
const runtime = parseLoose(process.argv[6], {});
const latest = Array.isArray(pipelines) ? pipelines[0] || {} : {};
const registryPresent = process.env.UNIDESK_PAC_DIAG_REGISTRY_PRESENT === 'true';
const sourceCommit = process.env.UNIDESK_PAC_DIAG_SOURCE_COMMIT || latest.sourceCommit || null;
const registryDigest = process.env.UNIDESK_PAC_DIAG_REGISTRY_DIGEST || null;
const expectedDigest = registryDigest || artifact.digest || null;
const runtimeImage = runtime.image || null;
const runtimeMatches = !expectedDigest || (typeof runtimeImage === 'string' && runtimeImage.includes(expectedDigest));
const gitopsReady = Boolean(artifact.gitopsCommit || argo.revision);
let code = 'pac-diagnostics-not-applicable';
let phase = 'not-applicable';
let ok = true;
let hint = 'consumer has no sentinel image_repository diagnostic config';
if (params.image_repository) {
if (!sourceCommit) {
ok = false; code = 'sentinel-pac-source-unknown'; phase = 'source-unknown'; hint = 'latest PaC PipelineRun did not expose a source commit';
} else if (!registryPresent) {
ok = false; code = 'sentinel-pac-registry-missing'; phase = 'source-ready-registry-missing'; hint = 'PaC source commit is known but the configured registry tag is missing';
} else if (!gitopsReady) {
ok = false; code = 'sentinel-pac-gitops-missing'; phase = 'registry-ready-gitops-missing'; hint = 'registry tag exists but GitOps revision is not visible in PaC artifact or Argo status';
} else if (argo.sync !== 'Synced' || argo.health !== 'Healthy') {
ok = false; code = 'sentinel-pac-argo-not-ready'; phase = 'gitops-ready-argo-pending'; hint = 'GitOps exists but Argo is not Synced/Healthy';
} else if (!runtimeMatches) {
ok = false; code = 'sentinel-pac-runtime-not-aligned'; phase = 'argo-ready-runtime-mismatch'; hint = 'runtime image does not match the registry digest observed for the source commit';
} else {
code = 'sentinel-pac-ready'; phase = 'ready'; hint = 'source, registry tag, GitOps, Argo and runtime are aligned';
}
}
process.stdout.write(JSON.stringify({
ok,
code,
phase,
hint,
sourceCommit,
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,
registry: {
present: registryPresent,
digest: registryDigest,
url: process.env.UNIDESK_PAC_DIAG_REGISTRY_URL || null,
},
gitops: {
branch: process.env.UNIDESK_PAC_DIAG_GITOPS_BRANCH || null,
manifestPath: process.env.UNIDESK_PAC_DIAG_GITOPS_MANIFEST_PATH || null,
commit: artifact.gitopsCommit || argo.revision || null,
},
argo: { sync: argo.sync || null, health: argo.health || null, revision: argo.revision || null },
runtime: { image: runtimeImage, digest: runtime.digest || null, readyReplicas: runtime.readyReplicas ?? null, replicas: runtime.replicas ?? null },
pipelineRun: latest.name || null,
valuesPrinted: false,
}));
NODE
rm -f "$params_file" "$pipelines_file" "$artifact_file" "$argo_file" "$runtime_file"
}
hook_summary() {
hooks=$(gitea_api GET "repos/$UNIDESK_PAC_GITEA_OWNER/$UNIDESK_PAC_GITEA_REPO/hooks" 2>/dev/null || echo '[]')
HOOKS_JSON="$hooks" node <<'NODE'
@@ -719,16 +885,18 @@ status_action() {
latest=$(printf '%s' "$pipelines" | node -e 'const fs=require("fs"); const a=JSON.parse(fs.readFileSync(0,"utf8")||"[]"); process.stdout.write(a[0]?.name||"")')
export UNIDESK_PAC_TARGET_PIPELINERUN="$latest"
tasks=$(task_rows)
artifact=$(artifact_summary)
artifact=$(json_normalize "$(artifact_summary)")
hooks=$(hook_summary)
argo=$(kubectl -n "$UNIDESK_PAC_ARGO_NAMESPACE" get application "$UNIDESK_PAC_ARGO_APPLICATION" -o json 2>/dev/null | node -e 'const fs=require("fs"); const s=fs.readFileSync(0,"utf8").trim(); if(!s){process.stdout.write("{}"); process.exit(0)} const a=JSON.parse(s); process.stdout.write(JSON.stringify({sync:a.status?.sync?.status||null, health:a.status?.health?.status||null, revision:a.status?.sync?.revision||null}))' || echo '{}')
runtime=$(runtime_summary)
printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","repositoryCondition":"%s","webhooks":%s,"pipelineRuns":%s,"taskRuns":%s,"artifact":%s,"argo":%s,"runtime":%s,"valuesPrinted":false}\n' \
argo=$(json_normalize "$argo")
runtime=$(json_normalize "$(runtime_summary)")
diagnostics=$(cicd_diagnostics "$pipelines" "$artifact" "$argo" "$runtime")
printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","repositoryCondition":"%s","webhooks":%s,"pipelineRuns":%s,"taskRuns":%s,"artifact":%s,"argo":%s,"runtime":%s,"diagnostics":%s,"valuesPrinted":false}\n' \
"$( [ -n "$crd" ] && [ "$controller_ready" != "0/0" ] && echo true || echo false )" \
"$( [ -n "$crd" ] && echo true || echo false )" \
"$(json_string "$controller_ready")" \
"$(json_string "$repository_condition")" \
"$hooks" "$pipelines" "$tasks" "$artifact" "$argo" "$runtime"
"$hooks" "$pipelines" "$tasks" "$artifact" "$argo" "$runtime" "$diagnostics"
}
history_action() {