|
|
|
@@ -5,8 +5,8 @@ import { startJob } from "./jobs";
|
|
|
|
|
import { runHwlabG14Command } from "./hwlab-g14";
|
|
|
|
|
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane } from "./hwlab-node-lanes";
|
|
|
|
|
|
|
|
|
|
type SecretAction = "status" | "ensure" | "cleanup-owned-postgres";
|
|
|
|
|
type SecretPreset = "openfga" | "master-server-admin-api-key" | "code-agent-provider" | "cloud-api-db" | "hwpod-db" | "owned-postgres-cleanup";
|
|
|
|
|
type SecretAction = "status" | "ensure" | "cleanup-owned-postgres" | "cleanup-obsolete";
|
|
|
|
|
type SecretPreset = "openfga" | "master-server-admin-api-key" | "code-agent-provider" | "cloud-api-db" | "owned-postgres-cleanup" | "obsolete-secret-cleanup";
|
|
|
|
|
type DelegatedNodeDomain = "control-plane" | "git-mirror";
|
|
|
|
|
|
|
|
|
|
interface NodeSecretOptions {
|
|
|
|
@@ -41,11 +41,9 @@ interface RuntimeSecretSpec {
|
|
|
|
|
cloudApiDbUser: string;
|
|
|
|
|
cloudApiDbHost: string;
|
|
|
|
|
cloudApiDeployment: string;
|
|
|
|
|
hwpodDbSecret: string;
|
|
|
|
|
hwpodDbKey: string;
|
|
|
|
|
hwpodDbName: string;
|
|
|
|
|
hwpodDbUser: string;
|
|
|
|
|
hwpodDbHost: string;
|
|
|
|
|
obsoleteHwpodDbSecret: string;
|
|
|
|
|
obsoleteHwpodDbName: string;
|
|
|
|
|
obsoleteHwpodDbUser: string;
|
|
|
|
|
codeAgentProviderSecret: string;
|
|
|
|
|
codeAgentProviderSourceNamespace: string;
|
|
|
|
|
codeAgentProviderSourceSecret: string;
|
|
|
|
@@ -92,9 +90,10 @@ export function hwlabNodeHelp(): Record<string, unknown> {
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-v03-openfga",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret ensure --node G14 --lane v03 --name hwlab-v03-master-server-admin-api-key --confirm",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-cloud-api-v03-db",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwpod-v03-db",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --dry-run",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node G14 --lane v03 --confirm",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret cleanup-obsolete --node G14 --lane v03 --name hwpod-v03-db --dry-run",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret cleanup-obsolete --node G14 --lane v03 --name hwpod-v03-db --confirm",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret status --node G14 --lane v03 --name hwlab-v03-code-agent-provider",
|
|
|
|
|
"bun scripts/cli.ts hwlab nodes secret ensure --node G14 --lane v03 --name hwlab-v03-code-agent-provider --confirm",
|
|
|
|
|
],
|
|
|
|
@@ -215,8 +214,8 @@ function rewriteDelegatedNodeString(value: string, scoped: ReturnType<typeof par
|
|
|
|
|
|
|
|
|
|
function parseSecretOptions(args: string[]): NodeSecretOptions {
|
|
|
|
|
const [actionRaw] = args;
|
|
|
|
|
if (actionRaw !== "status" && actionRaw !== "ensure" && actionRaw !== "cleanup-owned-postgres") {
|
|
|
|
|
throw new Error("secret usage: status|ensure --node NODE --lane vNN --name hwlab-vNN-openfga|hwlab-vNN-master-server-admin-api-key|hwlab-cloud-api-vNN-db|hwpod-vNN-db|hwlab-vNN-code-agent-provider [--dry-run|--confirm] | cleanup-owned-postgres --node NODE --lane vNN [--dry-run|--confirm]");
|
|
|
|
|
if (actionRaw !== "status" && actionRaw !== "ensure" && actionRaw !== "cleanup-owned-postgres" && actionRaw !== "cleanup-obsolete") {
|
|
|
|
|
throw new Error("secret usage: status|ensure --node NODE --lane vNN --name hwlab-vNN-openfga|hwlab-vNN-master-server-admin-api-key|hwlab-cloud-api-vNN-db|hwlab-vNN-code-agent-provider [--dry-run|--confirm] | cleanup-owned-postgres --node NODE --lane vNN [--dry-run|--confirm] | cleanup-obsolete --node NODE --lane vNN --name hwpod-vNN-db [--dry-run|--confirm]");
|
|
|
|
|
}
|
|
|
|
|
const node = requiredOption(args, "--node");
|
|
|
|
|
assertNodeId(node);
|
|
|
|
@@ -243,6 +242,21 @@ function parseSecretOptions(args: string[]): NodeSecretOptions {
|
|
|
|
|
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 900),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (actionRaw === "cleanup-obsolete") {
|
|
|
|
|
if (lane === "v02") throw new Error("secret cleanup-obsolete is only for v0.3+ lanes after deprecated v0.3 HWPOD DB SecretRef cleanup");
|
|
|
|
|
if (key !== undefined) throw new Error("secret cleanup-obsolete does not accept --key");
|
|
|
|
|
if (name !== spec.obsoleteHwpodDbSecret) throw new Error(`secret cleanup-obsolete for --lane ${lane} currently only targets obsolete ${spec.obsoleteHwpodDbSecret}`);
|
|
|
|
|
return {
|
|
|
|
|
action: actionRaw,
|
|
|
|
|
node,
|
|
|
|
|
lane,
|
|
|
|
|
name,
|
|
|
|
|
preset: "obsolete-secret-cleanup",
|
|
|
|
|
confirm,
|
|
|
|
|
dryRun: explicitDryRun || !confirm,
|
|
|
|
|
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 900),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (name === spec.masterAdminApiKeySecret) {
|
|
|
|
|
if (key !== undefined && key !== MASTER_ADMIN_API_KEY_KEY) throw new Error(`secret ${name} supports only key ${MASTER_ADMIN_API_KEY_KEY}`);
|
|
|
|
|
return {
|
|
|
|
@@ -290,26 +304,8 @@ function parseSecretOptions(args: string[]): NodeSecretOptions {
|
|
|
|
|
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 240, 900),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (name === spec.hwpodDbSecret) {
|
|
|
|
|
if (!spec.platformDb) throw new Error(`secret ${name} is only supported for v0.3+ platform DB lanes`);
|
|
|
|
|
if (key !== undefined && key !== spec.hwpodDbKey) throw new Error(`secret ${name} supports only key ${spec.hwpodDbKey}`);
|
|
|
|
|
if (actionRaw === "ensure") {
|
|
|
|
|
throw new Error(`secret ensure for ${name} on --lane ${lane} was removed after native platform DB migration; use status plus platform DB SecretRef rotation CLI when it exists`);
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
action: actionRaw,
|
|
|
|
|
node,
|
|
|
|
|
lane,
|
|
|
|
|
name,
|
|
|
|
|
key: key ?? spec.hwpodDbKey,
|
|
|
|
|
preset: "hwpod-db",
|
|
|
|
|
confirm,
|
|
|
|
|
dryRun: true,
|
|
|
|
|
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 240, 900),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (name !== spec.openFgaSecret) {
|
|
|
|
|
throw new Error(`secret status/ensure supports --name ${spec.openFgaSecret}, ${spec.masterAdminApiKeySecret}, ${spec.cloudApiDbSecret}, ${spec.hwpodDbSecret}, or ${spec.codeAgentProviderSecret} for --lane ${lane}`);
|
|
|
|
|
throw new Error(`secret status/ensure supports --name ${spec.openFgaSecret}, ${spec.masterAdminApiKeySecret}, ${spec.cloudApiDbSecret}, or ${spec.codeAgentProviderSecret} for --lane ${lane}; obsolete ${spec.obsoleteHwpodDbSecret} must use cleanup-obsolete`);
|
|
|
|
|
}
|
|
|
|
|
if (key !== undefined && key !== OPENFGA_AUTHN_KEY && key !== OPENFGA_DATASTORE_URI_KEY && key !== OPENFGA_POSTGRES_PASSWORD_KEY) {
|
|
|
|
|
throw new Error(`secret ${name} supports keys ${OPENFGA_AUTHN_KEY}, ${OPENFGA_DATASTORE_URI_KEY}, and ${OPENFGA_POSTGRES_PASSWORD_KEY}`);
|
|
|
|
@@ -356,11 +352,9 @@ function runtimeSecretSpec(input: { node: string; lane: string }): RuntimeSecret
|
|
|
|
|
cloudApiDbUser: platformDb ? `hwlab_${input.lane}_app` : `hwlab_${input.lane}`,
|
|
|
|
|
cloudApiDbHost: platformDb ? platformPostgresHost : legacyPostgresHost,
|
|
|
|
|
cloudApiDeployment: "hwlab-cloud-api",
|
|
|
|
|
hwpodDbSecret: `hwpod-${input.lane}-db`,
|
|
|
|
|
hwpodDbKey: CLOUD_API_DB_KEY,
|
|
|
|
|
hwpodDbName: `hwpod_${input.lane}`,
|
|
|
|
|
hwpodDbUser: platformDb ? `hwpod_${input.lane}_app` : `hwpod_${input.lane}`,
|
|
|
|
|
hwpodDbHost: platformDb ? platformPostgresHost : legacyPostgresHost,
|
|
|
|
|
obsoleteHwpodDbSecret: `hwpod-${input.lane}-db`,
|
|
|
|
|
obsoleteHwpodDbName: `hwpod_${input.lane}`,
|
|
|
|
|
obsoleteHwpodDbUser: `hwpod_${input.lane}_app`,
|
|
|
|
|
codeAgentProviderSecret: `${namespace}-code-agent-provider`,
|
|
|
|
|
codeAgentProviderSourceNamespace: CODE_AGENT_PROVIDER_SOURCE_NAMESPACE,
|
|
|
|
|
codeAgentProviderSourceSecret: CODE_AGENT_PROVIDER_SOURCE_SECRET,
|
|
|
|
@@ -370,6 +364,7 @@ function runtimeSecretSpec(input: { node: string; lane: string }): RuntimeSecret
|
|
|
|
|
|
|
|
|
|
function runNodeSecret(options: NodeSecretOptions): Record<string, unknown> {
|
|
|
|
|
const spec = runtimeSecretSpec(options);
|
|
|
|
|
if (options.preset === "obsolete-secret-cleanup") return runObsoleteSecretCleanup(options, spec);
|
|
|
|
|
const input = options.preset === "master-server-admin-api-key" && options.action === "ensure" && !options.dryRun
|
|
|
|
|
? readMasterAdminApiKey().key
|
|
|
|
|
: "";
|
|
|
|
@@ -377,7 +372,7 @@ function runNodeSecret(options: NodeSecretOptions): Record<string, unknown> {
|
|
|
|
|
? spec.platformDb ? platformDbSecretStatusScript(options, spec) : openFgaSecretScript(options, spec)
|
|
|
|
|
: options.preset === "master-server-admin-api-key"
|
|
|
|
|
? masterAdminApiKeySecretScript(options, spec)
|
|
|
|
|
: options.preset === "cloud-api-db" || options.preset === "hwpod-db"
|
|
|
|
|
: options.preset === "cloud-api-db"
|
|
|
|
|
? spec.platformDb ? platformDbSecretStatusScript(options, spec) : cloudApiDbSecretScript(options, spec)
|
|
|
|
|
: options.preset === "owned-postgres-cleanup"
|
|
|
|
|
? ownedPostgresCleanupScript(options, spec)
|
|
|
|
@@ -386,7 +381,8 @@ function runNodeSecret(options: NodeSecretOptions): Record<string, unknown> {
|
|
|
|
|
const status = secretStatusFromText(statusText(result), result.exitCode === 0, result.exitCode, result.stderr, spec);
|
|
|
|
|
const dryRunOk = options.action === "ensure" && options.dryRun && result.exitCode === 0;
|
|
|
|
|
const cleanupDryRunOk = options.action === "cleanup-owned-postgres" && options.dryRun && result.exitCode === 0;
|
|
|
|
|
const ok = dryRunOk || cleanupDryRunOk ? true : status.ok === true;
|
|
|
|
|
const obsoleteCleanupDryRunOk = options.action === "cleanup-obsolete" && options.dryRun && status.ok === true;
|
|
|
|
|
const ok = dryRunOk || cleanupDryRunOk || obsoleteCleanupDryRunOk ? true : status.ok === true;
|
|
|
|
|
return {
|
|
|
|
|
ok,
|
|
|
|
|
command: `hwlab nodes secret ${options.action}`,
|
|
|
|
@@ -396,7 +392,7 @@ function runNodeSecret(options: NodeSecretOptions): Record<string, unknown> {
|
|
|
|
|
secret: options.name,
|
|
|
|
|
key: options.key ?? null,
|
|
|
|
|
preset: options.preset,
|
|
|
|
|
mode: options.action === "status" ? "status" : options.dryRun ? "dry-run" : options.action === "cleanup-owned-postgres" ? "confirmed-delete" : "confirmed-ensure",
|
|
|
|
|
mode: options.action === "status" ? "status" : options.dryRun ? "dry-run" : options.action === "cleanup-owned-postgres" || options.action === "cleanup-obsolete" ? "confirmed-delete" : "confirmed-ensure",
|
|
|
|
|
status,
|
|
|
|
|
mutation: status.mutation === true,
|
|
|
|
|
result: compactCommandResult(result),
|
|
|
|
@@ -409,7 +405,10 @@ function nextSecretCommand(options: NodeSecretOptions, spec: RuntimeSecretSpec):
|
|
|
|
|
if (options.action === "cleanup-owned-postgres") {
|
|
|
|
|
return { ensure: `bun scripts/cli.ts hwlab nodes secret cleanup-owned-postgres --node ${options.node} --lane ${options.lane} --confirm` };
|
|
|
|
|
}
|
|
|
|
|
if (spec.platformDb && (options.preset === "cloud-api-db" || options.preset === "openfga" || options.preset === "hwpod-db")) {
|
|
|
|
|
if (options.action === "cleanup-obsolete") {
|
|
|
|
|
return { cleanup: `bun scripts/cli.ts hwlab nodes secret cleanup-obsolete --node ${options.node} --lane ${options.lane} --name ${options.name} --confirm` };
|
|
|
|
|
}
|
|
|
|
|
if (spec.platformDb && (options.preset === "cloud-api-db" || options.preset === "openfga")) {
|
|
|
|
|
return {
|
|
|
|
|
status: `bun scripts/cli.ts hwlab nodes secret status --node ${options.node} --lane ${options.lane} --name ${options.name}${options.key ? ` --key ${options.key}` : ""}`,
|
|
|
|
|
controlPlaneStatus: `bun scripts/cli.ts hwlab nodes control-plane status --node ${options.node} --lane ${options.lane}`,
|
|
|
|
@@ -422,6 +421,50 @@ function runTransScript(node: string, script: string, input: string, timeoutSeco
|
|
|
|
|
return runCommand(["/root/.local/bin/trans", `${node}:k3s`, "script", "--", script], repoRoot, { input, timeoutMs: timeoutSeconds * 1000 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runTransHostScript(node: string, script: string, input: string, timeoutSeconds: number): CommandResult {
|
|
|
|
|
return runCommand(["/root/.local/bin/trans", node, "script", "--", script], repoRoot, { input, timeoutMs: timeoutSeconds * 1000 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runObsoleteSecretCleanup(options: NodeSecretOptions, spec: RuntimeSecretSpec): Record<string, unknown> {
|
|
|
|
|
const kubernetesResult = runTransScript(options.node, obsoleteSecretCleanupScript(options, spec), "", options.timeoutSeconds);
|
|
|
|
|
const kubernetesStatus = secretStatusFromText(statusText(kubernetesResult), kubernetesResult.exitCode === 0, kubernetesResult.exitCode, kubernetesResult.stderr, spec);
|
|
|
|
|
const hostOptions = { ...options, dryRun: options.dryRun || kubernetesStatus.ok !== true };
|
|
|
|
|
const platformDbResult = runTransHostScript(options.node, obsoletePlatformDbCleanupScript(hostOptions, spec), "", options.timeoutSeconds);
|
|
|
|
|
const platformDbStatus = obsoletePlatformDbStatusFromText(statusText(platformDbResult), platformDbResult.exitCode === 0, platformDbResult.exitCode, platformDbResult.stderr, spec);
|
|
|
|
|
const ok = kubernetesStatus.ok === true && platformDbStatus.ok === true && (options.dryRun || hostOptions.dryRun === false);
|
|
|
|
|
const mutation = kubernetesStatus.mutation === true || platformDbStatus.mutation === true;
|
|
|
|
|
return {
|
|
|
|
|
ok,
|
|
|
|
|
command: `hwlab nodes secret ${options.action}`,
|
|
|
|
|
node: options.node,
|
|
|
|
|
lane: options.lane,
|
|
|
|
|
namespace: spec.namespace,
|
|
|
|
|
secret: options.name,
|
|
|
|
|
key: null,
|
|
|
|
|
preset: options.preset,
|
|
|
|
|
mode: options.dryRun ? "dry-run" : "confirmed-delete",
|
|
|
|
|
status: {
|
|
|
|
|
ok,
|
|
|
|
|
preset: "obsolete-hwpod-db-cleanup",
|
|
|
|
|
dryRun: options.dryRun,
|
|
|
|
|
mutation,
|
|
|
|
|
kubernetesSecret: kubernetesStatus,
|
|
|
|
|
platformDatabase: platformDbStatus,
|
|
|
|
|
hostMutationSkipped: !options.dryRun && hostOptions.dryRun,
|
|
|
|
|
summary: ok
|
|
|
|
|
? `${spec.obsoleteHwpodDbSecret}, ${spec.obsoleteHwpodDbName}, and ${spec.obsoleteHwpodDbUser} are absent or ready to remove`
|
|
|
|
|
: `${spec.obsoleteHwpodDbSecret}, ${spec.obsoleteHwpodDbName}, or ${spec.obsoleteHwpodDbUser} still needs cleanup`,
|
|
|
|
|
},
|
|
|
|
|
mutation,
|
|
|
|
|
result: {
|
|
|
|
|
kubernetesSecret: compactCommandResult(kubernetesResult),
|
|
|
|
|
platformDatabase: compactCommandResult(platformDbResult),
|
|
|
|
|
},
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
next: ok ? undefined : nextSecretCommand(options, spec),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function runNodeEndpointBridge(options: ReturnType<typeof parseNodeScopedDelegatedOptions>): Record<string, unknown> {
|
|
|
|
|
if (options.dryRun && options.confirm) throw new Error("control-plane allow-endpoint-bridge accepts only one of --dry-run or --confirm");
|
|
|
|
|
const dryRun = options.dryRun || !options.confirm;
|
|
|
|
@@ -796,15 +839,156 @@ function ownedPostgresCleanupScript(options: NodeSecretOptions, spec: RuntimeSec
|
|
|
|
|
].join("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function obsoleteSecretCleanupScript(options: NodeSecretOptions, spec: RuntimeSecretSpec): string {
|
|
|
|
|
return [
|
|
|
|
|
"set +e",
|
|
|
|
|
`namespace=${shellQuote(spec.namespace)}`,
|
|
|
|
|
`secret=${shellQuote(options.name)}`,
|
|
|
|
|
`expected_secret=${shellQuote(spec.obsoleteHwpodDbSecret)}`,
|
|
|
|
|
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
|
|
|
|
|
"preset=obsolete-secret-cleanup",
|
|
|
|
|
"exists_flag() { kubectl -n \"$namespace\" get secret \"$secret\" >/dev/null 2>&1 && printf yes || printf no; }",
|
|
|
|
|
"workload_refs=$(kubectl -n \"$namespace\" get deploy,statefulset,daemonset,job,cronjob -o yaml 2>/dev/null | grep -n -C 2 \"$secret\" || true)",
|
|
|
|
|
"workload_refs_present=$([ -n \"$workload_refs\" ] && printf yes || printf no)",
|
|
|
|
|
"workload_refs_preview=$(printf '%s' \"$workload_refs\" | sed -n '1,20p' | tr '\\n' ';' | cut -c1-1000)",
|
|
|
|
|
"before_exists=$(exists_flag)",
|
|
|
|
|
"action=observed",
|
|
|
|
|
"mutation=false",
|
|
|
|
|
"delete_secret_exit=",
|
|
|
|
|
"if [ \"$secret\" != \"$expected_secret\" ]; then action=unsupported-secret; fi",
|
|
|
|
|
"if [ \"$action\" = observed ]; then",
|
|
|
|
|
" if [ \"$workload_refs_present\" = yes ]; then",
|
|
|
|
|
" action=blocked-referenced",
|
|
|
|
|
" elif [ \"$dry_run\" = true ]; then",
|
|
|
|
|
" if [ \"$before_exists\" = yes ]; then action=would-delete; else action=already-absent; fi",
|
|
|
|
|
" else",
|
|
|
|
|
" kubectl -n \"$namespace\" delete secret \"$secret\" --ignore-not-found=true >/tmp/hwlab-obsolete-secret-delete.out 2>/tmp/hwlab-obsolete-secret-delete.err",
|
|
|
|
|
" delete_secret_exit=$?",
|
|
|
|
|
" for _ in $(seq 1 15); do",
|
|
|
|
|
" current_exists=$(exists_flag)",
|
|
|
|
|
" if [ \"$current_exists\" != yes ]; then break; fi",
|
|
|
|
|
" sleep 1",
|
|
|
|
|
" done",
|
|
|
|
|
" if [ \"$delete_secret_exit\" -eq 0 ]; then",
|
|
|
|
|
" if [ \"$before_exists\" = yes ]; then action=deleted; mutation=true; else action=already-absent; fi",
|
|
|
|
|
" else",
|
|
|
|
|
" action=delete-failed",
|
|
|
|
|
" fi",
|
|
|
|
|
" fi",
|
|
|
|
|
"fi",
|
|
|
|
|
"after_exists=$(exists_flag)",
|
|
|
|
|
"printf 'namespace\\t%s\\n' \"$namespace\"",
|
|
|
|
|
"printf 'secret\\t%s\\n' \"$secret\"",
|
|
|
|
|
"printf 'preset\\t%s\\n' \"$preset\"",
|
|
|
|
|
"printf 'action\\t%s\\n' \"$action\"",
|
|
|
|
|
"printf 'dryRun\\t%s\\n' \"$dry_run\"",
|
|
|
|
|
"printf 'mutation\\t%s\\n' \"$mutation\"",
|
|
|
|
|
"printf 'beforeSecretExists\\t%s\\n' \"$before_exists\"",
|
|
|
|
|
"printf 'afterSecretExists\\t%s\\n' \"$after_exists\"",
|
|
|
|
|
"printf 'workloadRefsPresent\\t%s\\n' \"$workload_refs_present\"",
|
|
|
|
|
"printf 'workloadRefsPreview\\t%s\\n' \"$workload_refs_preview\"",
|
|
|
|
|
"printf 'deleteSecretExitCode\\t%s\\n' \"$delete_secret_exit\"",
|
|
|
|
|
"if [ \"$action\" = unsupported-secret ]; then exit 43; fi",
|
|
|
|
|
"if [ \"$workload_refs_present\" = yes ]; then exit 46; fi",
|
|
|
|
|
"if [ \"$dry_run\" != true ] && [ \"$after_exists\" = yes ]; then exit 47; fi",
|
|
|
|
|
"if [ -n \"$delete_secret_exit\" ] && [ \"$delete_secret_exit\" != 0 ]; then exit \"$delete_secret_exit\"; fi",
|
|
|
|
|
].join("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function obsoletePlatformDbCleanupScript(options: NodeSecretOptions, spec: RuntimeSecretSpec): string {
|
|
|
|
|
return [
|
|
|
|
|
"set +e",
|
|
|
|
|
`db_name=${shellQuote(spec.obsoleteHwpodDbName)}`,
|
|
|
|
|
`db_user=${shellQuote(spec.obsoleteHwpodDbUser)}`,
|
|
|
|
|
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
|
|
|
|
|
"preset=obsolete-platform-db-cleanup",
|
|
|
|
|
"database_exists_flag() {",
|
|
|
|
|
" output=$(sudo -u postgres psql -d postgres -Atqc \"select exists(select 1 from pg_database where datname='$db_name');\" 2>/tmp/hwlab-obsolete-platform-db-probe.err)",
|
|
|
|
|
" code=$?",
|
|
|
|
|
" if [ \"$code\" -ne 0 ]; then printf unknown; return \"$code\"; fi",
|
|
|
|
|
" [ \"$output\" = t ] && printf yes || printf no",
|
|
|
|
|
"}",
|
|
|
|
|
"role_exists_flag() {",
|
|
|
|
|
" output=$(sudo -u postgres psql -d postgres -Atqc \"select exists(select 1 from pg_roles where rolname='$db_user');\" 2>/tmp/hwlab-obsolete-platform-role-probe.err)",
|
|
|
|
|
" code=$?",
|
|
|
|
|
" if [ \"$code\" -ne 0 ]; then printf unknown; return \"$code\"; fi",
|
|
|
|
|
" [ \"$output\" = t ] && printf yes || printf no",
|
|
|
|
|
"}",
|
|
|
|
|
"before_database_exists=$(database_exists_flag)",
|
|
|
|
|
"before_database_probe_exit=$?",
|
|
|
|
|
"before_role_exists=$(role_exists_flag)",
|
|
|
|
|
"before_role_probe_exit=$?",
|
|
|
|
|
"action=observed",
|
|
|
|
|
"mutation=false",
|
|
|
|
|
"drop_database_exit=",
|
|
|
|
|
"drop_role_exit=",
|
|
|
|
|
"before_any=false",
|
|
|
|
|
"if [ \"$before_database_exists\" = yes ] || [ \"$before_role_exists\" = yes ]; then before_any=true; fi",
|
|
|
|
|
"if [ \"$before_database_exists\" = unknown ] || [ \"$before_role_exists\" = unknown ]; then",
|
|
|
|
|
" action=probe-failed",
|
|
|
|
|
"elif [ \"$dry_run\" = true ]; then",
|
|
|
|
|
" if [ \"$before_any\" = true ]; then action=would-drop; else action=already-absent; fi",
|
|
|
|
|
"else",
|
|
|
|
|
" if [ \"$before_database_exists\" = yes ]; then",
|
|
|
|
|
" sudo -u postgres psql -v ON_ERROR_STOP=1 -d postgres -v db_name=\"$db_name\" >/tmp/hwlab-obsolete-platform-db-drop.out 2>/tmp/hwlab-obsolete-platform-db-drop.err <<'SQL'",
|
|
|
|
|
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = :'db_name' AND pid <> pg_backend_pid();",
|
|
|
|
|
"DROP DATABASE IF EXISTS :\"db_name\";",
|
|
|
|
|
"SQL",
|
|
|
|
|
" drop_database_exit=$?",
|
|
|
|
|
" else",
|
|
|
|
|
" drop_database_exit=0",
|
|
|
|
|
" fi",
|
|
|
|
|
" if [ \"$drop_database_exit\" -eq 0 ] && [ \"$before_role_exists\" = yes ]; then",
|
|
|
|
|
" sudo -u postgres psql -v ON_ERROR_STOP=1 -d postgres -v db_user=\"$db_user\" >/tmp/hwlab-obsolete-platform-role-drop.out 2>/tmp/hwlab-obsolete-platform-role-drop.err <<'SQL'",
|
|
|
|
|
"DROP ROLE IF EXISTS :\"db_user\";",
|
|
|
|
|
"SQL",
|
|
|
|
|
" drop_role_exit=$?",
|
|
|
|
|
" elif [ \"$drop_database_exit\" -eq 0 ]; then",
|
|
|
|
|
" drop_role_exit=0",
|
|
|
|
|
" else",
|
|
|
|
|
" drop_role_exit=",
|
|
|
|
|
" fi",
|
|
|
|
|
" if [ \"$drop_database_exit\" = 0 ] && [ \"$drop_role_exit\" = 0 ]; then",
|
|
|
|
|
" if [ \"$before_any\" = true ]; then action=dropped; mutation=true; else action=already-absent; fi",
|
|
|
|
|
" else",
|
|
|
|
|
" action=drop-failed",
|
|
|
|
|
" fi",
|
|
|
|
|
"fi",
|
|
|
|
|
"after_database_exists=$(database_exists_flag)",
|
|
|
|
|
"after_database_probe_exit=$?",
|
|
|
|
|
"after_role_exists=$(role_exists_flag)",
|
|
|
|
|
"after_role_probe_exit=$?",
|
|
|
|
|
"printf 'database\\t%s\\n' \"$db_name\"",
|
|
|
|
|
"printf 'role\\t%s\\n' \"$db_user\"",
|
|
|
|
|
"printf 'preset\\t%s\\n' \"$preset\"",
|
|
|
|
|
"printf 'action\\t%s\\n' \"$action\"",
|
|
|
|
|
"printf 'dryRun\\t%s\\n' \"$dry_run\"",
|
|
|
|
|
"printf 'mutation\\t%s\\n' \"$mutation\"",
|
|
|
|
|
"printf 'beforeDatabaseExists\\t%s\\n' \"$before_database_exists\"",
|
|
|
|
|
"printf 'beforeRoleExists\\t%s\\n' \"$before_role_exists\"",
|
|
|
|
|
"printf 'afterDatabaseExists\\t%s\\n' \"$after_database_exists\"",
|
|
|
|
|
"printf 'afterRoleExists\\t%s\\n' \"$after_role_exists\"",
|
|
|
|
|
"printf 'beforeDatabaseProbeExitCode\\t%s\\n' \"$before_database_probe_exit\"",
|
|
|
|
|
"printf 'beforeRoleProbeExitCode\\t%s\\n' \"$before_role_probe_exit\"",
|
|
|
|
|
"printf 'afterDatabaseProbeExitCode\\t%s\\n' \"$after_database_probe_exit\"",
|
|
|
|
|
"printf 'afterRoleProbeExitCode\\t%s\\n' \"$after_role_probe_exit\"",
|
|
|
|
|
"printf 'dropDatabaseExitCode\\t%s\\n' \"$drop_database_exit\"",
|
|
|
|
|
"printf 'dropRoleExitCode\\t%s\\n' \"$drop_role_exit\"",
|
|
|
|
|
"if [ \"$before_database_exists\" = unknown ] || [ \"$before_role_exists\" = unknown ] || [ \"$after_database_exists\" = unknown ] || [ \"$after_role_exists\" = unknown ]; then exit 49; fi",
|
|
|
|
|
"if [ \"$dry_run\" != true ] && { [ \"$after_database_exists\" = yes ] || [ \"$after_role_exists\" = yes ]; }; then exit 50; fi",
|
|
|
|
|
"if [ -n \"$drop_database_exit\" ] && [ \"$drop_database_exit\" != 0 ]; then exit \"$drop_database_exit\"; fi",
|
|
|
|
|
"if [ -n \"$drop_role_exit\" ] && [ \"$drop_role_exit\" != 0 ]; then exit \"$drop_role_exit\"; fi",
|
|
|
|
|
].join("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function platformDbSecretStatusScript(options: NodeSecretOptions, spec: RuntimeSecretSpec): string {
|
|
|
|
|
const isOpenFga = options.preset === "openfga";
|
|
|
|
|
const isHwpodDb = options.preset === "hwpod-db";
|
|
|
|
|
const platformEndpointSlice = `${spec.platformPostgresService}-host`;
|
|
|
|
|
return [
|
|
|
|
|
"set +e",
|
|
|
|
|
`namespace=${shellQuote(spec.namespace)}`,
|
|
|
|
|
`name=${shellQuote(isOpenFga ? spec.openFgaSecret : isHwpodDb ? spec.hwpodDbSecret : spec.cloudApiDbSecret)}`,
|
|
|
|
|
`database_url_key=${shellQuote(isOpenFga ? OPENFGA_DATASTORE_URI_KEY : isHwpodDb ? spec.hwpodDbKey : spec.cloudApiDbKey)}`,
|
|
|
|
|
`name=${shellQuote(isOpenFga ? spec.openFgaSecret : spec.cloudApiDbSecret)}`,
|
|
|
|
|
`database_url_key=${shellQuote(isOpenFga ? OPENFGA_DATASTORE_URI_KEY : spec.cloudApiDbKey)}`,
|
|
|
|
|
`authn_key=${shellQuote(OPENFGA_AUTHN_KEY)}`,
|
|
|
|
|
`postgres_password_key=${shellQuote(OPENFGA_POSTGRES_PASSWORD_KEY)}`,
|
|
|
|
|
`legacy_postgres_secret=${shellQuote(spec.postgresSecret)}`,
|
|
|
|
@@ -812,9 +996,9 @@ function platformDbSecretStatusScript(options: NodeSecretOptions, spec: RuntimeS
|
|
|
|
|
`platform_endpointslice=${shellQuote(platformEndpointSlice)}`,
|
|
|
|
|
`platform_host=${shellQuote(spec.platformPostgresService)}`,
|
|
|
|
|
`platform_host_fqdn=${shellQuote(spec.openFgaDbHost)}`,
|
|
|
|
|
`db_name=${shellQuote(isOpenFga ? spec.openFgaDbName : isHwpodDb ? spec.hwpodDbName : spec.cloudApiDbName)}`,
|
|
|
|
|
`db_user=${shellQuote(isOpenFga ? spec.openFgaDbUser : isHwpodDb ? spec.hwpodDbUser : spec.cloudApiDbUser)}`,
|
|
|
|
|
`db_host=${shellQuote(isOpenFga ? spec.openFgaDbHost : isHwpodDb ? spec.hwpodDbHost : spec.cloudApiDbHost)}`,
|
|
|
|
|
`db_name=${shellQuote(isOpenFga ? spec.openFgaDbName : spec.cloudApiDbName)}`,
|
|
|
|
|
`db_user=${shellQuote(isOpenFga ? spec.openFgaDbUser : spec.cloudApiDbUser)}`,
|
|
|
|
|
`db_host=${shellQuote(isOpenFga ? spec.openFgaDbHost : spec.cloudApiDbHost)}`,
|
|
|
|
|
`selected_key=${shellQuote(options.key ?? "")}`,
|
|
|
|
|
`preset=${shellQuote(options.preset)}`,
|
|
|
|
|
"dry_run=true",
|
|
|
|
@@ -1399,6 +1583,37 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
: `owned Postgres resources still exist in ${fields.namespace || spec.namespace}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (fields.preset === "obsolete-secret-cleanup") {
|
|
|
|
|
const absent = fields.afterSecretExists !== "yes";
|
|
|
|
|
const refsAbsent = fields.workloadRefsPresent !== "yes";
|
|
|
|
|
const dryRun = fields.dryRun === "true";
|
|
|
|
|
return {
|
|
|
|
|
ok: commandOk && refsAbsent && (dryRun || absent),
|
|
|
|
|
namespace: fields.namespace || spec.namespace,
|
|
|
|
|
secret: fields.secret || spec.obsoleteHwpodDbSecret,
|
|
|
|
|
preset: "obsolete-secret-cleanup",
|
|
|
|
|
action: fields.action || null,
|
|
|
|
|
dryRun,
|
|
|
|
|
mutation: fields.mutation === "true",
|
|
|
|
|
before: {
|
|
|
|
|
secretExists: fields.beforeSecretExists === "yes",
|
|
|
|
|
},
|
|
|
|
|
after: {
|
|
|
|
|
secretExists: fields.afterSecretExists === "yes",
|
|
|
|
|
},
|
|
|
|
|
workloadRefs: {
|
|
|
|
|
present: fields.workloadRefsPresent === "yes",
|
|
|
|
|
preview: fields.workloadRefsPreview || "",
|
|
|
|
|
},
|
|
|
|
|
deleteSecretExitCode: numericField(fields.deleteSecretExitCode),
|
|
|
|
|
exitCode,
|
|
|
|
|
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
summary: refsAbsent && (dryRun || absent)
|
|
|
|
|
? `${fields.secret || spec.obsoleteHwpodDbSecret} is unreferenced${dryRun ? "" : " and absent"}`
|
|
|
|
|
: `${fields.secret || spec.obsoleteHwpodDbSecret} still present or referenced`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (fields.preset === "master-server-admin-api-key") {
|
|
|
|
|
const afterBytes = numericField(fields.afterApiKeyBytes);
|
|
|
|
|
const healthy = fields.afterExists === "yes" && fields.afterApiKeyPresent === "yes" && typeof afterBytes === "number" && afterBytes > 0;
|
|
|
|
@@ -1462,13 +1677,7 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
summary: healthy ? `${fields.secret || spec.codeAgentProviderSecret} has a usable provider key` : `${fields.secret || spec.codeAgentProviderSecret} missing provider keys`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (fields.preset === "cloud-api-db" || fields.preset === "hwpod-db") {
|
|
|
|
|
const hwpodDb = fields.preset === "hwpod-db";
|
|
|
|
|
const expectedSecret = hwpodDb ? spec.hwpodDbSecret : spec.cloudApiDbSecret;
|
|
|
|
|
const expectedKey = hwpodDb ? spec.hwpodDbKey : spec.cloudApiDbKey;
|
|
|
|
|
const expectedDbName = hwpodDb ? spec.hwpodDbName : spec.cloudApiDbName;
|
|
|
|
|
const expectedDbUser = hwpodDb ? spec.hwpodDbUser : spec.cloudApiDbUser;
|
|
|
|
|
const expectedDbHost = hwpodDb ? spec.hwpodDbHost : spec.cloudApiDbHost;
|
|
|
|
|
if (fields.preset === "cloud-api-db") {
|
|
|
|
|
const beforeUrlBytes = numericField(fields.beforeDatabaseUrlBytes);
|
|
|
|
|
const afterUrlBytes = numericField(fields.afterDatabaseUrlBytes);
|
|
|
|
|
if (fields.platformDbMode === "true") {
|
|
|
|
@@ -1485,9 +1694,9 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
return {
|
|
|
|
|
ok: commandOk && healthy,
|
|
|
|
|
namespace: fields.namespace || spec.namespace,
|
|
|
|
|
secret: fields.secret || expectedSecret,
|
|
|
|
|
key: fields.key || expectedKey,
|
|
|
|
|
preset: hwpodDb ? "hwpod-db" : "cloud-api-db",
|
|
|
|
|
secret: fields.secret || spec.cloudApiDbSecret,
|
|
|
|
|
key: fields.key || spec.cloudApiDbKey,
|
|
|
|
|
preset: "cloud-api-db",
|
|
|
|
|
action: fields.action || null,
|
|
|
|
|
dryRun: fields.dryRun === "true",
|
|
|
|
|
mutation: fields.mutation === "true",
|
|
|
|
@@ -1508,9 +1717,9 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
endpointSlice: fields.platformEndpointSlice || `${spec.platformPostgresService}-host`,
|
|
|
|
|
endpointSliceExists: fields.platformEndpointSliceExists === "yes",
|
|
|
|
|
},
|
|
|
|
|
dbName: fields.dbName || expectedDbName,
|
|
|
|
|
dbUser: fields.dbUser || expectedDbUser,
|
|
|
|
|
dbHost: fields.dbHost || expectedDbHost,
|
|
|
|
|
dbName: fields.dbName || spec.cloudApiDbName,
|
|
|
|
|
dbUser: fields.dbUser || spec.cloudApiDbUser,
|
|
|
|
|
dbHost: fields.dbHost || spec.cloudApiDbHost,
|
|
|
|
|
dbHostMatchesPlatform: fields.dbHostMatchesPlatform === "yes",
|
|
|
|
|
dbNameMatchesExpected: fields.dbNameMatchesExpected === "yes",
|
|
|
|
|
dbUserMatchesExpected: fields.dbUserMatchesExpected === "yes",
|
|
|
|
@@ -1518,24 +1727,8 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
summary: healthy
|
|
|
|
|
? `${fields.secret || expectedSecret}/${fields.key || expectedKey} points to ${fields.platformService || spec.platformPostgresService}`
|
|
|
|
|
: `${fields.secret || expectedSecret}/${fields.key || expectedKey} is not aligned to platform DB`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (hwpodDb) {
|
|
|
|
|
return {
|
|
|
|
|
ok: false,
|
|
|
|
|
namespace: fields.namespace || spec.namespace,
|
|
|
|
|
secret: fields.secret || expectedSecret,
|
|
|
|
|
key: fields.key || expectedKey,
|
|
|
|
|
preset: "hwpod-db",
|
|
|
|
|
action: fields.action || null,
|
|
|
|
|
dryRun: fields.dryRun === "true",
|
|
|
|
|
mutation: false,
|
|
|
|
|
exitCode,
|
|
|
|
|
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
summary: "HWPOD DB Secret status is only supported for native platform DB lanes",
|
|
|
|
|
? `${fields.secret || spec.cloudApiDbSecret}/${fields.key || spec.cloudApiDbKey} points to ${fields.platformService || spec.platformPostgresService}`
|
|
|
|
|
: `${fields.secret || spec.cloudApiDbSecret}/${fields.key || spec.cloudApiDbKey} is not aligned to platform DB`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const keysHealthy = fields.afterExists === "yes" &&
|
|
|
|
@@ -1546,8 +1739,8 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
return {
|
|
|
|
|
ok: commandOk && healthy,
|
|
|
|
|
namespace: fields.namespace || spec.namespace,
|
|
|
|
|
secret: fields.secret || expectedSecret,
|
|
|
|
|
key: fields.key || expectedKey,
|
|
|
|
|
secret: fields.secret || spec.cloudApiDbSecret,
|
|
|
|
|
key: fields.key || spec.cloudApiDbKey,
|
|
|
|
|
preset: "cloud-api-db",
|
|
|
|
|
action: fields.action || null,
|
|
|
|
|
dryRun: fields.dryRun === "true",
|
|
|
|
@@ -1573,9 +1766,9 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
},
|
|
|
|
|
postgresAdminSecretPresent: fields.postgresAdminSecretPresent === "yes",
|
|
|
|
|
postgresSecret: fields.postgresSecret || spec.postgresSecret,
|
|
|
|
|
dbName: fields.dbName || expectedDbName,
|
|
|
|
|
dbUser: fields.dbUser || expectedDbUser,
|
|
|
|
|
dbHost: fields.dbHost || expectedDbHost,
|
|
|
|
|
dbName: fields.dbName || spec.cloudApiDbName,
|
|
|
|
|
dbUser: fields.dbUser || spec.cloudApiDbUser,
|
|
|
|
|
dbHost: fields.dbHost || spec.cloudApiDbHost,
|
|
|
|
|
cloudApiDeployment: fields.cloudApiDeployment || spec.cloudApiDeployment,
|
|
|
|
|
applyExitCode: numericField(fields.applyExitCode),
|
|
|
|
|
dbEnsureExitCode: numericField(fields.dbEnsureExitCode),
|
|
|
|
@@ -1584,7 +1777,7 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
exitCode,
|
|
|
|
|
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
summary: healthy ? `${fields.secret || expectedSecret}/${fields.key || expectedKey} exists and runtime database is present` : `${fields.secret || expectedSecret}/${fields.key || expectedKey} or runtime database missing`,
|
|
|
|
|
summary: healthy ? `${fields.secret || spec.cloudApiDbSecret}/${fields.key || spec.cloudApiDbKey} exists and runtime database is present` : `${fields.secret || spec.cloudApiDbSecret}/${fields.key || spec.cloudApiDbKey} or runtime database missing`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const afterAuthnBytes = numericField(fields.afterAuthnBytes);
|
|
|
|
@@ -1681,6 +1874,50 @@ function secretStatusFromText(text: string, commandOk: boolean, exitCode: number
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function obsoletePlatformDbStatusFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string, spec: RuntimeSecretSpec): Record<string, unknown> {
|
|
|
|
|
const fields = keyValueLinesFromText(text);
|
|
|
|
|
const dryRun = fields.dryRun === "true";
|
|
|
|
|
const databaseAbsent = fields.afterDatabaseExists !== "yes" && fields.afterDatabaseExists !== "unknown";
|
|
|
|
|
const roleAbsent = fields.afterRoleExists !== "yes" && fields.afterRoleExists !== "unknown";
|
|
|
|
|
const probesOk = fields.beforeDatabaseExists !== "unknown" &&
|
|
|
|
|
fields.beforeRoleExists !== "unknown" &&
|
|
|
|
|
fields.afterDatabaseExists !== "unknown" &&
|
|
|
|
|
fields.afterRoleExists !== "unknown";
|
|
|
|
|
return {
|
|
|
|
|
ok: commandOk && probesOk && (dryRun || (databaseAbsent && roleAbsent)),
|
|
|
|
|
database: fields.database || spec.obsoleteHwpodDbName,
|
|
|
|
|
role: fields.role || spec.obsoleteHwpodDbUser,
|
|
|
|
|
preset: "obsolete-platform-db-cleanup",
|
|
|
|
|
action: fields.action || null,
|
|
|
|
|
dryRun,
|
|
|
|
|
mutation: fields.mutation === "true",
|
|
|
|
|
before: {
|
|
|
|
|
databaseExists: fields.beforeDatabaseExists === "yes",
|
|
|
|
|
roleExists: fields.beforeRoleExists === "yes",
|
|
|
|
|
},
|
|
|
|
|
after: {
|
|
|
|
|
databaseExists: fields.afterDatabaseExists === "yes",
|
|
|
|
|
roleExists: fields.afterRoleExists === "yes",
|
|
|
|
|
},
|
|
|
|
|
beforeProbeExitCode: {
|
|
|
|
|
database: numericField(fields.beforeDatabaseProbeExitCode),
|
|
|
|
|
role: numericField(fields.beforeRoleProbeExitCode),
|
|
|
|
|
},
|
|
|
|
|
afterProbeExitCode: {
|
|
|
|
|
database: numericField(fields.afterDatabaseProbeExitCode),
|
|
|
|
|
role: numericField(fields.afterRoleProbeExitCode),
|
|
|
|
|
},
|
|
|
|
|
dropDatabaseExitCode: numericField(fields.dropDatabaseExitCode),
|
|
|
|
|
dropRoleExitCode: numericField(fields.dropRoleExitCode),
|
|
|
|
|
exitCode,
|
|
|
|
|
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
|
|
|
|
|
valuesRedacted: true,
|
|
|
|
|
summary: probesOk && (dryRun || (databaseAbsent && roleAbsent))
|
|
|
|
|
? `${fields.database || spec.obsoleteHwpodDbName} and ${fields.role || spec.obsoleteHwpodDbUser} are ${dryRun ? "observable" : "absent"}`
|
|
|
|
|
: `${fields.database || spec.obsoleteHwpodDbName} or ${fields.role || spec.obsoleteHwpodDbUser} still present or unobservable`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function nodeSecretStatusFromTextForTest(text: string, commandOk: boolean, exitCode: number | null, stderr: string, node = "G14", lane = "v03"): Record<string, unknown> {
|
|
|
|
|
return secretStatusFromText(text, commandOk, exitCode, stderr, runtimeSecretSpec({ node, lane }));
|
|
|
|
|
}
|
|
|
|
|