diff --git a/docs/MDTODO/pikaoa-enterprise-platform.md b/docs/MDTODO/pikaoa-enterprise-platform.md index e340c520..01e22dc5 100644 --- a/docs/MDTODO/pikaoa-enterprise-platform.md +++ b/docs/MDTODO/pikaoa-enterprise-platform.md @@ -39,6 +39,9 @@ #### R1.5.2 [in_progress] 扩展 NC01 共享 observability,使 OTel/Tempo 与 Prometheus 由同一 YAML-first 控制面管理并提供跨 namespace 指标抓取和有界查询,执行记录见 [pikasTech/unidesk#1956](https://github.com/pikasTech/unidesk/issues/1956),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.2_Task_Report.md)。 +#### R1.5.3 [in_progress] + +修复 `platform-db postgres plan` 默认 30KB JSON 被输出预算截断的可见性缺陷,提供数据库、角色、Secret/export、检查与下一步的紧凑 CLI-first 投影,执行记录沿用 [主 issue](https://github.com/pikasTech/unidesk/issues/1952),完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.5.3_Task_Report.md)。 ### R1.6 审核并合并双仓 PR,执行首次受控 PaC bootstrap,由正常 source PR merge 自动发布,依赖 R1.3-R1.5,完成任务后将详细报告写入[任务报告](./details/pikaoa-enterprise-platform/R1.6_Task_Report.md)。 diff --git a/scripts/src/platform-db.ts b/scripts/src/platform-db.ts index 96141acd..95496255 100644 --- a/scripts/src/platform-db.ts +++ b/scripts/src/platform-db.ts @@ -302,7 +302,7 @@ export function platformDbHelp(): unknown { command: "platform-db postgres plan|status|apply|export-secrets", output: "json", usage: [ - `bun scripts/cli.ts platform-db postgres plan --config ${defaultConfigPath}`, + `bun scripts/cli.ts platform-db postgres plan --config ${defaultConfigPath} [--full|--raw]`, `bun scripts/cli.ts platform-db postgres status --config ${defaultConfigPath} [--full|--raw]`, `bun scripts/cli.ts platform-db postgres export-secrets --config ${defaultConfigPath} --dry-run`, `bun scripts/cli.ts platform-db postgres export-secrets --config ${defaultConfigPath} --confirm`, @@ -439,14 +439,15 @@ async function plan(config: UniDeskConfig, options: PlatformDbOptions): Promise< const remote = await remoteFacts(config, pg, null); const facts = remote.parsed; const checks = facts === null ? [] : desiredChecks(pg, facts, secrets); - return { + const desired = desiredSummary(pg, secrets, facts); + const result = { ok: remote.capture.exitCode === 0 && facts !== null && secrets.ok, action: "platform-db-postgres-plan", mutation: false, config: configSummary(pg), secrets: secretSummary(secrets), remoteFacts: facts, - desired: desiredSummary(pg, secrets, facts), + desired, checks, next: { apply: `bun scripts/cli.ts platform-db postgres apply --config ${pg.configPath} --confirm`, @@ -456,6 +457,8 @@ async function plan(config: UniDeskConfig, options: PlatformDbOptions): Promise< remote: compactCapture(remote.capture, { full: options.full || remote.capture.exitCode !== 0 }), ...(options.raw ? { raw: remote.capture } : {}), }; + if (options.full) return result; + return compactPlan(pg, secrets, facts, checks, remote.capture, result.ok); } async function status(config: UniDeskConfig, options: PlatformDbOptions): Promise> { @@ -1071,6 +1074,135 @@ function configSummary(pg: PostgresHostConfig): Record { }; } +function compactPlan( + pg: PostgresHostConfig, + secrets: SecretInspection, + facts: RemoteFacts | null, + checks: Array>, + capture: SshCaptureResult, + ok: boolean, +): Record { + const observedRoles = facts?.postgres.roles ?? []; + const observedDatabases = facts?.postgres.databases ?? []; + return { + ok, + action: "platform-db-postgres-plan", + mutation: false, + target: { + node: pg.node.id, + route: pg.node.route, + mode: pg.node.mode, + }, + config: { + path: pg.configPath, + id: pg.metadata.id, + pgVersion: pg.postgres.package.version, + connectionHost: pg.postgres.network.connectionHost, + publicDns: pg.postgres.network.publicDns, + port: pg.postgres.network.port, + transport: pg.postgres.network.transport, + sslmode: pg.postgres.network.sslmode, + }, + roles: pg.objects.roles.map((role) => ({ + name: role.name, + exists: observedRoles.find((item) => item.name === role.name)?.exists ?? null, + action: observedRoles.some((item) => item.name === role.name && item.exists) ? "none" : "create-or-update", + })), + databases: pg.objects.databases.map((database) => ({ + name: database.name, + owner: database.owner, + exists: observedDatabases.find((item) => item.name === database.name)?.exists ?? null, + action: observedDatabases.some((item) => item.name === database.name && item.exists) ? "none" : "create", + })), + secrets: { + ok: secrets.ok, + root: secrets.root, + entries: secrets.entries.map((entry) => ({ + sourceRef: entry.sourceRef, + missingKeys: entry.missingKeys, + action: entry.action, + })), + valuesPrinted: false, + }, + exports: pg.exports.connectionStrings.map((item) => ({ + name: item.name, + sourceRef: item.sourceSecretRef, + targetRef: item.writeToSecretSource.sourceRef, + key: item.writeToSecretSource.key, + consumerScopes: item.consumers.map((consumer) => consumer.scope), + })), + checks: { + ok: checks.every((check) => check.ok === true), + passed: checks.filter((check) => check.ok === true).length, + total: checks.length, + items: checks.map((check) => ({ + name: check.name, + ok: check.ok, + ...(check.ok === true ? {} : { detail: compactText(JSON.stringify(check)).slice(0, 480) }), + })), + }, + remoteFacts: compactRemoteFacts(facts), + remote: compactPlanCapture(capture), + next: { + apply: `bun scripts/cli.ts platform-db postgres apply --config ${pg.configPath} --confirm`, + status: `bun scripts/cli.ts platform-db postgres status --config ${pg.configPath}`, + full: `bun scripts/cli.ts platform-db postgres plan --config ${pg.configPath} --full`, + raw: `bun scripts/cli.ts platform-db postgres plan --config ${pg.configPath} --raw`, + issues: pg.metadata.relatedIssues.map((issue) => `https://github.com/pikasTech/unidesk/issues/${issue}`), + }, + disclosure: { + compact: true, + omitted: ["package", "host policy", "TLS paths", "backup", "full remote facts"], + fullAvailable: true, + rawAvailable: true, + }, + valuesPrinted: false, + }; +} + +function compactRemoteFacts(facts: RemoteFacts | null): Record | null { + if (facts === null) return null; + return { + ok: facts.ok, + observedAt: facts.observedAt, + host: { + hostname: facts.host.hostname, + cpuCount: facts.host.cpuCount, + memoryTotalMiB: facts.host.memoryTotalMiB, + swapTotalMiB: facts.host.swapTotalMiB, + rootAvailGiB: facts.host.rootAvailGiB, + targetDataAvailGiB: facts.host.targetDataAvailGiB, + }, + network: { + port5432Listening: facts.network.port5432Listening, + dns: facts.network.dns, + }, + repo: { + reachable: facts.repo.reachable, + releaseUrl: facts.repo.releaseUrl, + }, + postgres: { + packageInstalled: facts.postgres.packageInstalled, + serviceActive: facts.postgres.serviceActive, + serviceEnabled: facts.postgres.serviceEnabled, + roleCount: facts.postgres.roles.length, + databaseCount: facts.postgres.databases.length, + serverVersion: facts.postgres.serverVersion, + sslOn: facts.postgres.sslOn, + }, + }; +} + +function compactPlanCapture(result: SshCaptureResult): Record { + return { + exitCode: result.exitCode, + stdoutBytes: Buffer.byteLength(result.stdout, "utf8"), + stderrBytes: Buffer.byteLength(result.stderr, "utf8"), + stdoutTail: result.exitCode === 0 ? "" : result.stdout.slice(-1200), + stderrTail: result.exitCode === 0 ? "" : result.stderr.slice(-1200), + }; +} + function requireMonitorResetConfig(pg: PostgresHostConfig): MonitorResetConfig { const target = pg.managedOperations?.monitorReset; if (target === undefined) throw new Error(`${pg.configPath}.managedOperations.monitorReset is required`);