396 lines
17 KiB
TypeScript
396 lines
17 KiB
TypeScript
import type { RenderedCliResult } from "../output";
|
|
|
|
import type { SecretSyncOptions } from "./options";
|
|
import { displayValue, renderTable } from "./options";
|
|
import { record, stringOrNull } from "./utils";
|
|
|
|
const MAX_PLAN_ITEMS = 14;
|
|
const MAX_TARGET_SECRETS = 4;
|
|
const MAX_ACTIVATION_RESOURCES = 4;
|
|
|
|
export function renderAgentRunSecretSyncResult(result: Record<string, unknown>, options: SecretSyncOptions): RenderedCliResult {
|
|
const command = "agentrun control-plane secret-sync";
|
|
if (options.full || options.raw) return renderSecretSyncMachine(command, result, options.output === "yaml" && !options.raw ? "yaml" : "json");
|
|
const payload = compactAgentRunSecretSyncPayload(result);
|
|
if (options.output === "json" || options.output === "yaml") return renderSecretSyncMachine(command, payload, options.output);
|
|
return {
|
|
ok: result.ok !== false,
|
|
command,
|
|
renderedText: `${renderAgentRunSecretSyncText(payload)}\n`,
|
|
contentType: "text/plain",
|
|
};
|
|
}
|
|
|
|
export function compactAgentRunSecretSyncPayload(result: Record<string, unknown>): Record<string, unknown> {
|
|
const target = record(result.target);
|
|
const targetNode = record(target.node);
|
|
const identity = {
|
|
node: boundedText(targetNode.id ?? target.node),
|
|
lane: boundedText(target.lane),
|
|
namespace: boundedText(target.namespace ?? record(target.runtime).namespace),
|
|
valuesPrinted: false,
|
|
};
|
|
const plan = record(result.plan);
|
|
const rawItems = records(plan.items);
|
|
const planItems = rawItems.slice(0, MAX_PLAN_ITEMS).map((item) => compactSecretPlanItem(item, identity));
|
|
const requestedSecretIds = record(result.filter).secretIds;
|
|
const secretIds = Array.isArray(requestedSecretIds)
|
|
? requestedSecretIds.map((item) => boundedText(item)).filter((item): item is string => item !== null)
|
|
: [];
|
|
const baseCommand = secretSyncBaseCommand(identity, secretIds);
|
|
const activationPreflight = record(result.activationPreflight);
|
|
return {
|
|
apiVersion: "unidesk.pikastech.local/v1alpha1",
|
|
kind: "AgentRunSecretSyncPlan",
|
|
identity,
|
|
status: {
|
|
ok: result.ok !== false,
|
|
status: boundedText(result.status ?? (result.ok === false ? "blocked" : "ready")),
|
|
mode: boundedText(result.mode),
|
|
reason: boundedText(result.degradedReason),
|
|
blockedBeforeMutation: result.blockedBeforeMutation === true,
|
|
secretSyncStarted: result.secretSyncStarted === true || result.mutation === true,
|
|
mutation: result.mutation === true,
|
|
valuesPrinted: false,
|
|
},
|
|
selection: {
|
|
requestedSecretIds: secretIds,
|
|
selectedCount: rawItems.length,
|
|
valuesPrinted: false,
|
|
},
|
|
plan: {
|
|
secretCount: numberOr(plan.secretCount, rawItems.length),
|
|
readyCount: numberOrNull(plan.readyCount),
|
|
unavailableCount: numberOrNull(plan.unavailableCount),
|
|
items: planItems,
|
|
omittedCount: Math.max(0, numberOr(plan.secretCount, rawItems.length) - planItems.length),
|
|
valuesPrinted: false,
|
|
},
|
|
activationFence: Object.keys(activationPreflight).length === 0
|
|
? null
|
|
: compactActivationFence(activationPreflight, identity),
|
|
execution: compactExecution(record(result.execution)),
|
|
next: {
|
|
secretById: `${secretSyncBaseCommand(identity, [])} --secret-id <secret-id> --dry-run -o json`,
|
|
observeRunners: boundedText(record(result.next).observeRunners),
|
|
retryDryRun: boundedText(record(result.next).retryDryRun),
|
|
retryAfterManagerFence: boundedText(record(result.next).retryAfterManagerFence),
|
|
confirm: boundedText(record(result.next).confirm),
|
|
status: boundedText(record(result.next).status),
|
|
full: `${baseCommand} --dry-run --full -o json`,
|
|
raw: `${baseCommand} --dry-run --raw`,
|
|
valuesPrinted: false,
|
|
},
|
|
redaction: {
|
|
secretValues: "omitted",
|
|
runtimeSecretValuesDecoded: false,
|
|
valuesPrinted: false,
|
|
},
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
export function renderAgentRunSecretSyncText(payload: Record<string, unknown>): string {
|
|
const identity = record(payload.identity);
|
|
const status = record(payload.status);
|
|
const plan = record(payload.plan);
|
|
const planItems = records(plan.items);
|
|
const fence = record(payload.activationFence);
|
|
const next = record(payload.next);
|
|
const lines = [
|
|
"AGENTRUN SECRET SYNC",
|
|
renderTable(
|
|
["NODE", "LANE", "NAMESPACE", "STATUS", "MODE", "MUTATION", "VALUES"],
|
|
[[
|
|
displayValue(identity.node),
|
|
displayValue(identity.lane),
|
|
displayValue(identity.namespace),
|
|
displayValue(status.status),
|
|
displayValue(status.mode),
|
|
String(status.mutation === true),
|
|
"false",
|
|
]],
|
|
),
|
|
`Reason: ${displayValue(status.reason)}`,
|
|
"",
|
|
`SECRET PLAN count=${displayValue(plan.secretCount)} omitted=${displayValue(plan.omittedCount)}`,
|
|
planItems.length === 0
|
|
? "-"
|
|
: renderTable(["ID", "TARGET", "KEY", "SOURCE", "PRESENT", "FINGERPRINT", "STATUS"], planItems.map((item) => {
|
|
const source = record(item.source);
|
|
const target = record(item.target);
|
|
return [
|
|
displayValue(item.id),
|
|
displayValue(target.identity),
|
|
displayValue(target.key),
|
|
displayValue(source.mode),
|
|
displayValue(source.present),
|
|
shortFingerprint(source.fingerprint),
|
|
source.present === true ? "ready" : "unavailable",
|
|
];
|
|
})),
|
|
];
|
|
if (Object.keys(fence).length > 0) {
|
|
const authority = record(fence.authority);
|
|
const observation = record(fence.observation);
|
|
const targetSecrets = records(fence.targetSecrets);
|
|
const resources = records(fence.resources);
|
|
lines.push(
|
|
"",
|
|
"ACTIVATION FENCE",
|
|
renderTable(["IDENTITY", "TYPE", "STATUS", "REASON", "RISKS", "ATOMIC"], [[
|
|
displayValue(fence.identity),
|
|
displayValue(fence.type),
|
|
displayValue(fence.status),
|
|
displayValue(fence.reason),
|
|
displayValue(fence.resourceCount),
|
|
displayValue(authority.atomicFenceAvailable),
|
|
]]),
|
|
`Observation: targets=${displayValue(observation.targetSecretObservationComplete)} runners=${displayValue(observation.runnerObservationComplete)} mutations=${displayValue(observation.providerSecretMutationCount)} noOp=${displayValue(observation.providerSecretDataNoOp)}`,
|
|
);
|
|
if (targetSecrets.length > 0) {
|
|
lines.push(
|
|
"",
|
|
"FENCE TARGET SECRETS",
|
|
renderTable(["IDENTITY", "KEYS", "PRESENT", "STATUS", "MUTATIONS"], targetSecrets.map((item) => [
|
|
displayValue(item.identity),
|
|
stringList(item.desiredKeys),
|
|
displayValue(item.present),
|
|
displayValue(item.status),
|
|
displayValue(item.mutationCount),
|
|
])),
|
|
);
|
|
}
|
|
if (resources.length > 0) {
|
|
lines.push(
|
|
"",
|
|
"FENCE RESOURCES",
|
|
renderTable(["IDENTITY", "STATUS", "RUN", "COMMAND", "CLASSIFICATION"], resources.map((item) => [
|
|
displayValue(item.identity),
|
|
displayValue(item.status),
|
|
displayValue(item.runId),
|
|
displayValue(item.commandId),
|
|
displayValue(item.classification),
|
|
])),
|
|
...resources.flatMap((item) => records(item.drillDown).map((entry) => ` ${displayValue(item.identity)}: ${displayValue(entry.command)}`)),
|
|
);
|
|
}
|
|
}
|
|
const nextLines = ["secretById", "observeRunners", "retryDryRun", "retryAfterManagerFence", "confirm", "status", "full", "raw"]
|
|
.map((key) => boundedText(next[key]))
|
|
.filter((value): value is string => value !== null);
|
|
lines.push("", "NEXT", ...nextLines.map((line) => ` ${line}`), "", "VALUES PRINTED false");
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function compactSecretPlanItem(item: Record<string, unknown>, identity: Record<string, unknown>): Record<string, unknown> {
|
|
const id = boundedText(item.id);
|
|
const namespace = boundedText(item.namespace);
|
|
const secret = boundedText(item.secret);
|
|
const key = boundedText(item.key);
|
|
void identity;
|
|
return {
|
|
id,
|
|
target: {
|
|
identity: namespace === null || secret === null ? null : `secret/${namespace}/${secret}`,
|
|
key,
|
|
},
|
|
source: {
|
|
mode: boundedText(item.sourceMode),
|
|
present: item.present === true,
|
|
fingerprint: boundedText(item.fingerprint, 96),
|
|
...(boundedText(item.degradedReason) === null ? {} : { reason: boundedText(item.degradedReason) }),
|
|
},
|
|
};
|
|
}
|
|
|
|
function compactActivationFence(preflight: Record<string, unknown>, identity: Record<string, unknown>): Record<string, unknown> {
|
|
const authority = record(preflight.managerAuthority);
|
|
const nestedAuthority = record(preflight.activationAuthority);
|
|
const targetSecrets = records(preflight.targetSecrets).slice(0, MAX_TARGET_SECRETS).map(compactFenceTargetSecret);
|
|
const rawRisks = records(preflight.activationRisks);
|
|
const resources = rawRisks.slice(0, MAX_ACTIVATION_RESOURCES).map((risk, index) => compactFenceResource(risk, identity, index));
|
|
return {
|
|
apiVersion: "unidesk.pikastech.local/v1alpha1",
|
|
kind: "ProviderSecretActivationFence",
|
|
identity: `providersecretactivationfence/${displayValue(identity.node)}/${displayValue(identity.lane)}`,
|
|
type: "provider-secret-activation",
|
|
status: boundedText(preflight.status ?? (preflight.ok === true ? "ready" : "blocked")),
|
|
reason: boundedText(preflight.reason),
|
|
requiresManagerFence: preflight.requiresManagerFence === true,
|
|
authority: {
|
|
owner: boundedText(authority.owner ?? nestedAuthority.owner),
|
|
atomicFenceAvailable: authority.atomicFenceAvailable === true || nestedAuthority.atomicFenceAvailable === true,
|
|
mutationAuthorized: authority.mutationAuthorized === true || preflight.providerSecretMutationAuthorized === true,
|
|
noOpOnly: authority.noOpOnly !== false && nestedAuthority.noOpOnly !== false,
|
|
trackingIssue: boundedText(authority.trackingIssue),
|
|
},
|
|
observation: {
|
|
targetSecretObservationComplete: preflight.targetSecretObservationComplete === true,
|
|
runnerObservationComplete: preflight.runnerObservationComplete === true,
|
|
allObservationsComplete: preflight.allObservationsComplete === true,
|
|
providerSecretMutationCount: numberOrNull(preflight.providerSecretMutationCount),
|
|
providerSecretDataNoOp: preflight.providerSecretDataNoOp === true,
|
|
},
|
|
targetSecretCount: numberOr(preflight.targetSecretCount, targetSecrets.length),
|
|
targetSecrets,
|
|
targetSecretOmittedCount: Math.max(0, numberOr(preflight.targetSecretCount, targetSecrets.length) - targetSecrets.length),
|
|
resourceCount: numberOr(preflight.activationRiskCount, rawRisks.length),
|
|
resources,
|
|
resourceOmittedCount: Math.max(0, numberOr(preflight.activationRiskCount, rawRisks.length) - resources.length),
|
|
valuesPrinted: false,
|
|
};
|
|
}
|
|
|
|
function compactFenceTargetSecret(item: Record<string, unknown>): Record<string, unknown> {
|
|
const namespace = boundedText(item.namespace);
|
|
const name = boundedText(item.name);
|
|
const mutations = records(item.mutations).slice(0, 6).map((mutation) => {
|
|
const id = boundedText(mutation.id);
|
|
return {
|
|
id,
|
|
key: boundedText(mutation.key),
|
|
changeKind: boundedText(mutation.changeKind),
|
|
desiredFingerprintSuffix: boundedText(mutation.desiredFingerprintSuffix ?? mutation.desiredEncodedFingerprint, 24)?.slice(-12) ?? null,
|
|
runtimeFingerprintSuffix: boundedText(mutation.runtimeFingerprintSuffix ?? mutation.runtimeEncodedFingerprint, 24)?.slice(-12) ?? null,
|
|
};
|
|
});
|
|
const queryOk = item.queryOk === true;
|
|
const mutationCount = numberOr(item.mutationCount, mutations.length);
|
|
return {
|
|
kind: "Secret",
|
|
identity: namespace === null || name === null ? null : `secret/${namespace}/${name}`,
|
|
namespace,
|
|
name,
|
|
desiredKeys: stringArray(item.desiredKeys, 8),
|
|
queryOk,
|
|
present: typeof item.present === "boolean" ? item.present : null,
|
|
status: !queryOk ? "observation-incomplete" : mutationCount === 0 ? "ready-no-op" : "mutation-pending",
|
|
mutationCount,
|
|
mutations,
|
|
};
|
|
}
|
|
|
|
function compactFenceResource(risk: Record<string, unknown>, identity: Record<string, unknown>, index: number): Record<string, unknown> {
|
|
const namespace = boundedText(identity.namespace);
|
|
const runId = boundedText(risk.runId);
|
|
const commandId = boundedText(risk.commandId);
|
|
const runnerJobId = boundedText(risk.runnerJobId);
|
|
const jobName = boundedText(risk.jobName);
|
|
const pod = boundedText(risk.pod);
|
|
const resourceIdentity = runnerJobId !== null
|
|
? `runnerjob/${runnerJobId}`
|
|
: jobName !== null && namespace !== null
|
|
? `job/${namespace}/${jobName}`
|
|
: pod !== null && namespace !== null
|
|
? `pod/${namespace}/${pod}`
|
|
: `activationrisk/${index + 1}`;
|
|
const drillDown = fenceResourceDrillDown(identity, { runId, commandId, runnerJobId });
|
|
return {
|
|
identity: resourceIdentity,
|
|
status: boundedText(risk.phase),
|
|
classification: boundedText(risk.classification),
|
|
runId,
|
|
commandId,
|
|
runnerJobId,
|
|
waitingReasons: stringArray(risk.waitingReasons, 4),
|
|
dependencies: records(risk.dependencies).slice(0, 3).map((dependency) => ({
|
|
identity: boundedText(dependency.name) === null || namespace === null ? null : `secret/${namespace}/${boundedText(dependency.name)}`,
|
|
source: boundedText(dependency.source),
|
|
affectedKeys: stringArray(dependency.affectedDesiredKeys, 6),
|
|
mutationKinds: stringArray(dependency.mutationKinds, 4),
|
|
reason: boundedText(dependency.reason),
|
|
})),
|
|
drillDown,
|
|
};
|
|
}
|
|
|
|
function fenceResourceDrillDown(
|
|
identity: Record<string, unknown>,
|
|
resource: { runId: string | null; commandId: string | null; runnerJobId: string | null },
|
|
): Record<string, unknown>[] {
|
|
const nodeLane = nodeLaneArgs(identity);
|
|
const result: Record<string, unknown>[] = [];
|
|
if (resource.runId !== null && resource.runnerJobId !== null) {
|
|
result.push({ kind: "RunnerJob", identity: `runnerjob/${resource.runnerJobId}`, command: `bun scripts/cli.ts agentrun describe runnerjob/${resource.runnerJobId} --run ${resource.runId}${nodeLane}` });
|
|
}
|
|
if (resource.runId !== null && resource.commandId !== null) {
|
|
result.push({ kind: "Command", identity: `command/${resource.commandId}`, command: `bun scripts/cli.ts agentrun describe command/${resource.commandId} --run ${resource.runId}${nodeLane}` });
|
|
}
|
|
if (resource.runId !== null) {
|
|
result.push({ kind: "Run", identity: `run/${resource.runId}`, command: `bun scripts/cli.ts agentrun events run/${resource.runId} --after-seq 0 --limit 100${nodeLane}` });
|
|
}
|
|
return result.slice(0, 3);
|
|
}
|
|
|
|
function compactExecution(value: Record<string, unknown>): Record<string, unknown> {
|
|
return {
|
|
providerSecretClassification: boundedText(value.providerSecretClassification),
|
|
providerSecretWritePolicy: boundedText(value.providerSecretWritePolicy),
|
|
providerSecretWriteBlockedCount: numberOrNull(value.providerSecretWriteBlockedCount),
|
|
providerSecretWriteSkippedCount: numberOrNull(value.providerSecretWriteSkippedCount),
|
|
writableNonProviderSecretCount: numberOrNull(value.writableNonProviderSecretCount),
|
|
secretSyncStarted: value.secretSyncStarted === true,
|
|
};
|
|
}
|
|
|
|
function secretSyncBaseCommand(identity: Record<string, unknown>, secretIds: readonly string[]): string {
|
|
const node = boundedText(identity.node) ?? "<node>";
|
|
const lane = boundedText(identity.lane) ?? "<lane>";
|
|
return [
|
|
"bun scripts/cli.ts agentrun control-plane secret-sync",
|
|
`--node ${node}`,
|
|
`--lane ${lane}`,
|
|
...secretIds.map((id) => `--secret-id ${id}`),
|
|
].join(" ");
|
|
}
|
|
|
|
function nodeLaneArgs(identity: Record<string, unknown>): string {
|
|
const node = boundedText(identity.node);
|
|
const lane = boundedText(identity.lane);
|
|
return node === null || lane === null ? "" : ` --node ${node} --lane ${lane}`;
|
|
}
|
|
|
|
function renderSecretSyncMachine(command: string, value: unknown, mode: "json" | "yaml"): RenderedCliResult {
|
|
return {
|
|
ok: record(value).ok !== false && record(record(value).status).ok !== false,
|
|
command,
|
|
renderedText: mode === "json" ? `${JSON.stringify(value)}\n` : `${Bun.YAML.stringify(value)}\n`,
|
|
contentType: mode === "json" ? "application/json" : "application/yaml",
|
|
};
|
|
}
|
|
|
|
function records(value: unknown): Record<string, unknown>[] {
|
|
return Array.isArray(value) ? value.map(record).filter((item) => Object.keys(item).length > 0) : [];
|
|
}
|
|
|
|
function boundedText(value: unknown, max = 240): string | null {
|
|
const text = stringOrNull(value);
|
|
if (text === null) return null;
|
|
const oneLine = text.replace(/\s+/gu, " ").trim();
|
|
return oneLine.length <= max ? oneLine : `${oneLine.slice(0, Math.max(0, max - 3))}...`;
|
|
}
|
|
|
|
function stringArray(value: unknown, max: number): string[] {
|
|
if (!Array.isArray(value)) return [];
|
|
return value.map((item) => boundedText(item, 120)).filter((item): item is string => item !== null).slice(0, max);
|
|
}
|
|
|
|
function numberOr(value: unknown, fallback: number): number {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
function numberOrNull(value: unknown): number | null {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
}
|
|
|
|
function shortFingerprint(value: unknown): string {
|
|
const fingerprint = boundedText(value, 96);
|
|
if (fingerprint === null) return "-";
|
|
return fingerprint.length <= 20 ? fingerprint : `${fingerprint.slice(0, 7)}…${fingerprint.slice(-12)}`;
|
|
}
|
|
|
|
function stringList(value: unknown): string {
|
|
return Array.isArray(value) && value.length > 0 ? value.map(String).join(",") : "-";
|
|
}
|