From 356b8bf6ac34325930cdd445caee734ceb05ba23 Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 6 Jul 2026 01:14:15 +0000 Subject: [PATCH] fix: make pac history read large namespaces --- ...platform-infra-pipelines-as-code-remote.sh | 51 +++++++++++++++---- .../src/platform-infra-pipelines-as-code.ts | 12 ++++- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/scripts/src/platform-infra-pipelines-as-code-remote.sh b/scripts/src/platform-infra-pipelines-as-code-remote.sh index 6649b2d0..cb500840 100644 --- a/scripts/src/platform-infra-pipelines-as-code-remote.sh +++ b/scripts/src/platform-infra-pipelines-as-code-remote.sh @@ -266,6 +266,7 @@ const limit = Math.max(1, Math.min(50, Number.parseInt(process.env.UNIDESK_PAC_H const detailId = process.env.UNIDESK_PAC_HISTORY_ID || ''; const consumers = JSON.parse(Buffer.from(process.env.UNIDESK_PAC_HISTORY_CONSUMERS_B64 || 'W10=', 'base64').toString('utf8')); const displayTimeZone = process.env.UNIDESK_PAC_DISPLAY_TIME_ZONE || 'UTC'; +const kubectlJsonErrors = []; const displayTimeFormatter = new Intl.DateTimeFormat('sv-SE', { timeZone: displayTimeZone, year: 'numeric', @@ -277,17 +278,44 @@ const displayTimeFormatter = new Intl.DateTimeFormat('sv-SE', { hour12: false, }); -function kubectlJson(args, fallback) { +function kubectlJson(args, fallback, context) { + const tmpDir = fs.mkdtempSync('/tmp/unidesk-pac-history-'); + const outPath = `${tmpDir}/kubectl.json`; + let outFd = null; try { - const out = cp.execFileSync('kubectl', args, { + outFd = fs.openSync(outPath, 'w'); + const result = cp.spawnSync('kubectl', args, { + stdio: ['ignore', outFd, 'pipe'], encoding: 'utf8', - stdio: ['ignore', 'pipe', 'ignore'], timeout: 30000, - maxBuffer: 10 * 1024 * 1024, + maxBuffer: 1024 * 1024, }); + fs.closeSync(outFd); + outFd = null; + if (result.error || result.status !== 0) { + kubectlJsonErrors.push({ + context: context || args.join(' '), + status: result.status, + error: result.error ? String(result.error.message || result.error) : null, + stderr: String(result.stderr || '').slice(0, 1000), + }); + return fallback; + } + const out = fs.readFileSync(outPath, 'utf8'); return JSON.parse(out || JSON.stringify(fallback)); - } catch { + } catch (error) { + kubectlJsonErrors.push({ + context: context || args.join(' '), + status: null, + error: String(error && error.message ? error.message : error).slice(0, 1000), + stderr: null, + }); return fallback; + } finally { + if (outFd !== null) { + try { fs.closeSync(outFd); } catch {} + } + try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch {} } } @@ -474,8 +502,8 @@ function rowFor(consumer, item, taskRuns) { const rows = []; const consumerSummaries = []; for (const consumer of consumers) { - const pipelineRuns = kubectlJson(['-n', consumer.namespace, 'get', 'pipelinerun', '-o', 'json'], { items: [] }); - const taskRuns = kubectlJson(['-n', consumer.namespace, 'get', 'taskrun', '-o', 'json'], { items: [] }); + const pipelineRuns = kubectlJson(['-n', consumer.namespace, 'get', 'pipelinerun', '-o', 'json'], { items: [] }, `${consumer.id}:pipelinerun`); + const taskRuns = kubectlJson(['-n', consumer.namespace, 'get', 'taskrun', '-o', 'json'], { items: [] }, `${consumer.id}:taskrun`); let matches = (pipelineRuns.items || []).filter((item) => { const labels = item.metadata?.labels || {}; const name = item.metadata?.name || ''; @@ -487,10 +515,10 @@ for (const consumer of consumers) { matches = matches.sort((a, b) => Date.parse(b.metadata?.creationTimestamp || 0) - Date.parse(a.metadata?.creationTimestamp || 0)); if (!detailId) matches = matches.slice(0, limit); for (const item of matches) rows.push(rowFor(consumer, item, taskRuns)); - consumerSummaries.push({ id: consumer.id, namespace: consumer.namespace, repository: consumer.repository, matched: matches.length }); + consumerSummaries.push({ id: consumer.id, namespace: consumer.namespace, repository: consumer.repository, pipelineRunPrefix: consumer.pipelineRunPrefix, totalPipelineRuns: (pipelineRuns.items || []).length, matched: matches.length }); } rows.sort((a, b) => Date.parse(b.triggeredAt || 0) - Date.parse(a.triggeredAt || 0)); -process.stdout.write(JSON.stringify({ rows, consumers: consumerSummaries })); +process.stdout.write(JSON.stringify({ rows, consumers: consumerSummaries, errors: kubectlJsonErrors })); NODE } @@ -672,7 +700,8 @@ history_action() { history=$(history_rows) rows=$(printf '%s' "$history" | node -e 'const fs=require("fs"); const h=JSON.parse(fs.readFileSync(0,"utf8")||"{}"); process.stdout.write(JSON.stringify(h.rows||[]));') consumers=$(printf '%s' "$history" | node -e 'const fs=require("fs"); const h=JSON.parse(fs.readFileSync(0,"utf8")||"{}"); process.stdout.write(JSON.stringify(h.consumers||[]));') - printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","repositoryCondition":"%s","repository":{"name":"%s","repo":"%s/%s","url":"%s"},"consumer":"%s","display":{"timeZone":"%s"},"consumerRows":%s,"rows":%s,"webhooks":%s,"source":"gitea-pac-tekton-live","historyStore":"none","valuesPrinted":false}\n' \ + errors=$(printf '%s' "$history" | node -e 'const fs=require("fs"); const h=JSON.parse(fs.readFileSync(0,"utf8")||"{}"); process.stdout.write(JSON.stringify(h.errors||[]));') + printf '{"ok":%s,"crdPresent":%s,"controllerReady":"%s","repositoryCondition":"%s","repository":{"name":"%s","repo":"%s/%s","url":"%s"},"consumer":"%s","display":{"timeZone":"%s"},"consumerRows":%s,"rows":%s,"historyErrors":%s,"webhooks":%s,"source":"gitea-pac-tekton-live","historyStore":"none","valuesPrinted":false}\n' \ "$( [ -n "$crd" ] && [ "$controller_ready" != "0/0" ] && echo true || echo false )" \ "$( [ -n "$crd" ] && echo true || echo false )" \ "$(json_string "$controller_ready")" \ @@ -683,7 +712,7 @@ history_action() { "$(json_string "$UNIDESK_PAC_REPOSITORY_URL")" \ "$(json_string "$UNIDESK_PAC_CONSUMER_ID")" \ "$(json_string "$UNIDESK_PAC_DISPLAY_TIME_ZONE")" \ - "$consumers" "$rows" "$hooks" + "$consumers" "$rows" "$errors" "$hooks" } webhook_test_action() { diff --git a/scripts/src/platform-infra-pipelines-as-code.ts b/scripts/src/platform-infra-pipelines-as-code.ts index b35d0e17..e19f8e2b 100644 --- a/scripts/src/platform-infra-pipelines-as-code.ts +++ b/scripts/src/platform-infra-pipelines-as-code.ts @@ -425,8 +425,9 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise< const result = await capture(config, target.route, ["sh"], remoteScript("history", pac, target, firstRepository, firstConsumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, "", selectedConsumers)); const parsed = parseJsonOutput(result.stdout); const remote = parsed ?? compactCapture(result, { full: true }); + const historyErrors = arrayRecords(record(remote).historyErrors); return { - ok: result.exitCode === 0 && parsed?.ok !== false, + ok: result.exitCode === 0 && parsed?.ok !== false && historyErrors.length === 0, action: "platform-infra-pipelines-as-code-history", mutation: false, target: targetSummary(target), @@ -440,6 +441,7 @@ async function history(config: UniDeskConfig, options: HistoryOptions): Promise< consumers: selectedConsumers.map((consumer) => consumer.id), detailId: options.detailId, rows: arrayRecords(record(remote).rows), + historyErrors, remote, next: nextCommands(target.id, options.consumerId ?? pac.defaults.consumerId, pac.defaults.consumerId), }; @@ -786,12 +788,18 @@ function renderStatus(result: Record): RenderedCliResult { function renderHistory(result: Record): RenderedCliResult { const rows = arrayRecords(result.rows); const config = record(result.config); + const historyErrors = arrayRecords(result.historyErrors); const detailId = stringValue(result.detailId); const timeHeader = `TIME(${stringValue(config.displayTimeZone)})`; const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE HISTORY", `DATA: ${stringValue(config.source)}`, - `ROWS: ${stringValue(rows.length)} LIMIT_PER_CONSUMER: ${stringValue(config.limitPerConsumer)}`, + `ROWS: ${stringValue(rows.length)} LIMIT_PER_CONSUMER: ${stringValue(config.limitPerConsumer)} READ_ERRORS: ${stringValue(historyErrors.length)}`, + ...(historyErrors.length === 0 ? [] : [ + "", + "READ ERRORS", + ...table(["CONTEXT", "STATUS", "ERROR"], historyErrors.map((item) => [stringValue(item.context), stringValue(item.status), compactLine(stringValue(item.error ?? item.stderr))])), + ]), "", "TRIGGERS", ...(rows.length === 0 ? ["-"] : table([timeHeader, "CONSUMER", "REPO", "STATUS", "DUR_S", "COMMIT", "ENV_REUSE", "ID"], rows.map((row) => [