Files
pikasTech-unidesk/scripts/src/platform-infra-observability/actions.ts
T
Lyon edfddd2445 fix: YAML-first 治理 CI/CD target (#919)
* docs: specify cicd yaml target governance

* fix: resolve cicd targets from yaml

---------

Co-authored-by: Codex <codex@noreply.local>
2026-06-26 01:14:38 +08:00

164 lines
8.3 KiB
TypeScript

// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. actions module for scripts/src/platform-infra-observability.ts.
// SPEC: PJ2026-01060308 cicd-yaml-targets draft-2026-06-25-cicd-yaml-targets.
// Moved mechanically from scripts/src/platform-infra-observability.ts:515-623 for #903.
// SPEC: PJ2026-01060501 OTel追踪 draft-2026-06-19-p0.
// Responsibility: YAML-first platform-infra OpenTelemetry tracing control commands.
import { Buffer } from "node:buffer";
import { readFileSync } from "node:fs";
import type { UniDeskConfig } from "../config";
import { rootPath } from "../config";
import type { RenderedCliResult } from "../output";
import {
compactCapture,
createYamlFieldReader,
numberField,
parseJsonOutput,
redactSensitiveUnknown,
shQuote,
capture,
} from "../platform-infra-ops-library";
import type { ApplyOptions, CommonOptions, ObservabilityConfig, ObservabilityTarget, StatusEndpoint } from "./types";
import { readObservabilityConfig } from "./config";
import { applyScript, statusScript } from "./search-script";
import { compactStatus, configSummary, manifestObjectSummary, policyChecks, statusSummary, targetSummary } from "./summary";
import { renderManifest } from "./trace-script";
import { apiPathField, configLabel, kubernetesNameField, stringField } from "./types";
export function parseStatusEndpoint(record: Record<string, unknown>, index: number): StatusEndpoint {
const path = `probes.statusEndpoints[${index}]`;
return {
name: stringField(record, "name", path),
service: kubernetesNameField(record, "service", path),
portName: stringField(record, "portName", path),
path: apiPathField(record, "path", path),
};
}
export function assertKnownEnabledTarget(targets: ObservabilityTarget[], targetId: string, path: string): void {
const target = targets.find((item) => item.id.toLowerCase() === targetId.toLowerCase());
if (target === undefined) throw new Error(`${configLabel}.${path} references unknown target ${targetId}; known targets: ${targets.map((item) => item.id).join(", ")}`);
if (!target.enabled) throw new Error(`${configLabel}.${path} references disabled target ${target.id}`);
}
export function resolveTarget(observability: ObservabilityConfig, targetId: string | null): ObservabilityTarget {
const resolved = targetId ?? observability.defaults.targetId;
const target = observability.targets.find((item) => item.id.toLowerCase() === resolved.toLowerCase());
if (target === undefined) throw new Error(`unknown observability target ${resolved}; known targets: ${observability.targets.map((item) => item.id).join(", ")}`);
if (!target.enabled) throw new Error(`observability target ${target.id} is disabled in ${configLabel}`);
return target;
}
export function plan(options: CommonOptions): Record<string, unknown> {
const observability = readObservabilityConfig();
const target = resolveTarget(observability, options.targetId);
const yaml = renderManifest(observability, target);
const policy = policyChecks(yaml, target);
return {
ok: policy.every((check) => check.ok),
action: "platform-infra-observability-plan",
mutation: false,
config: configSummary(observability, target),
renderPlan: {
target: targetSummary(target),
objects: manifestObjectSummary(yaml),
otlp: {
collectorGrpcEndpoint: `${observability.collector.serviceName}.${target.namespace}.svc.cluster.local:${observability.collector.otlp.grpcPort}`,
collectorHttpEndpoint: `http://${observability.collector.serviceName}.${target.namespace}.svc.cluster.local:${observability.collector.otlp.httpPort}`,
backendGrpcEndpoint: `${observability.traceBackend.serviceName}.${target.namespace}.svc.cluster.local:${observability.traceBackend.otlp.grpcPort}`,
},
instrumentation: observability.instrumentation.serviceConnections,
resourceAttributes: observability.resourceAttributes,
},
policy,
next: {
dryRun: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --dry-run`,
apply: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --confirm`,
status: `bun scripts/cli.ts platform-infra observability status --target ${target.id}`,
validate: `bun scripts/cli.ts platform-infra observability validate --target ${target.id}`,
trace: `bun scripts/cli.ts platform-infra observability trace --target ${target.id} --trace-id <traceId>`,
},
};
}
export async function apply(config: UniDeskConfig, options: ApplyOptions): Promise<Record<string, unknown>> {
const observability = readObservabilityConfig();
const target = resolveTarget(observability, options.targetId);
const yaml = renderManifest(observability, target);
const policy = policyChecks(yaml, target);
if (!policy.every((check) => check.ok)) return { ok: false, action: "platform-infra-observability-apply", mode: "policy-blocked", policy };
const result = await capture(config, target.route, ["sh"], applyScript({
yaml,
target,
dryRun: options.dryRun,
wait: options.wait,
collectorDeploymentName: observability.collector.deploymentName,
backendDeploymentName: observability.traceBackend.deploymentName,
}));
const parsed = parseJsonOutput(result.stdout);
return {
ok: result.exitCode === 0 && parsed?.ok === true,
action: "platform-infra-observability-apply",
mode: options.dryRun ? "dry-run" : "confirmed",
mutation: !options.dryRun,
target: targetSummary(target),
policy,
remote: parsed ?? compactCapture(result, { full: true }),
};
}
export async function status(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const observability = readObservabilityConfig();
const target = resolveTarget(observability, options.targetId);
const result = await capture(config, target.route, ["sh"], statusScript(observability, target, options.full));
const parsed = parseJsonOutput(result.stdout);
const summary = parsed === null ? null : statusSummary(parsed);
return {
ok: result.exitCode === 0 && summary?.ready === true,
action: "platform-infra-observability-status",
mutation: false,
target: targetSummary(target),
config: configSummary(observability, target),
summary,
remote: options.raw ? parsed : compactStatus(parsed, options.full) ?? compactCapture(result, { full: true }),
next: {
plan: `bun scripts/cli.ts platform-infra observability plan --target ${target.id}`,
apply: `bun scripts/cli.ts platform-infra observability apply --target ${target.id} --confirm`,
validate: `bun scripts/cli.ts platform-infra observability validate --target ${target.id}`,
},
};
}
export async function validate(config: UniDeskConfig, options: CommonOptions): Promise<Record<string, unknown>> {
const observability = readObservabilityConfig();
const target = resolveTarget(observability, options.targetId);
const result = await capture(config, target.route, ["sh"], statusScript(observability, target, options.full, true));
const parsed = parseJsonOutput(result.stdout);
const summary = parsed === null ? null : statusSummary(parsed);
const ready = summary?.ready === true;
const validationTrace = parsed !== null && typeof parsed.validationTrace === "object" && parsed.validationTrace !== null && !Array.isArray(parsed.validationTrace)
? parsed.validationTrace as Record<string, unknown>
: null;
const validationTraceId = typeof validationTrace?.traceId === "string" ? validationTrace.traceId : null;
const validationTraceOk = validationTrace?.ok === true;
return {
ok: result.exitCode === 0 && ready && validationTraceOk,
action: "platform-infra-observability-validate",
mutation: false,
target: targetSummary(target),
config: configSummary(observability, target),
summary,
validation: {
readiness: ready ? "passed" : "failed",
testTrace: validationTrace ?? "not-generated-runtime-not-ready",
traceQuery: validationTraceId !== null
? `bun scripts/cli.ts platform-infra observability trace --target ${target.id} --trace-id ${validationTraceId}`
: "blocked-until-validation-trace-generated",
metricsBoundary: "Prometheus/RUM remains outside this trace readiness check.",
},
remote: options.raw ? parsed : compactStatus(parsed, options.full) ?? compactCapture(result, { full: true }),
};
}