refactor: reuse common ops primitives
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { rootPath } from "./config";
|
||||
import {
|
||||
asRecord,
|
||||
arrayField,
|
||||
booleanField,
|
||||
integerField,
|
||||
optionalIntegerField,
|
||||
optionalStringField,
|
||||
readYamlRecord,
|
||||
recordField,
|
||||
stringArrayField,
|
||||
stringField,
|
||||
} from "./platform-infra-ops-library";
|
||||
|
||||
export const AGENTRUN_CONFIG_PATH = "config/agentrun.yaml";
|
||||
|
||||
@@ -355,7 +366,7 @@ export function agentRunPipelineRunName(spec: AgentRunLaneSpec, sourceCommit: st
|
||||
|
||||
function readAgentRunControlPlaneConfig(env: NodeJS.ProcessEnv): AgentRunControlPlaneConfig {
|
||||
const configPath = env.AGENTRUN_CONTROL_PLANE_CONFIG ?? rootPath(AGENTRUN_CONFIG_PATH);
|
||||
const root = asRecord(Bun.YAML.parse(readFileSync(configPath, "utf8")) as unknown, configPath);
|
||||
const root = readYamlRecord<Record<string, unknown>>(configPath);
|
||||
validateConfigEnvelope(root, configPath);
|
||||
const controlPlane = recordField(root, "controlPlane", configPath);
|
||||
const defaultTarget = recordField(controlPlane, "default", `${configPath}.controlPlane`);
|
||||
@@ -648,62 +659,6 @@ function optionalEnvPair(obj: Record<string, unknown>, key: string, path: string
|
||||
};
|
||||
}
|
||||
|
||||
function asRecord(value: unknown, path: string): Record<string, unknown> {
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be a YAML object`);
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
function recordField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
|
||||
return asRecord(obj[key], `${path}.${key}`);
|
||||
}
|
||||
|
||||
function stringField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function optionalStringField(obj: Record<string, unknown>, key: string, path: string): string | undefined {
|
||||
const value = obj[key];
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string when set`);
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
function booleanField(obj: Record<string, unknown>, key: string, path: string): boolean {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "boolean") throw new Error(`${path}.${key} must be a boolean`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function integerField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = obj[key];
|
||||
if (!Number.isInteger(value)) throw new Error(`${path}.${key} must be an integer`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
function optionalIntegerField(obj: Record<string, unknown>, key: string, path: string): number | undefined {
|
||||
const value = obj[key];
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (!Number.isInteger(value)) throw new Error(`${path}.${key} must be an integer when set`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
function arrayField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown>[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value)) throw new Error(`${path}.${key} must be a YAML array`);
|
||||
return value.map((item, index) => asRecord(item, `${path}.${key}[${index}]`));
|
||||
}
|
||||
|
||||
function stringArrayField(obj: Record<string, unknown>, key: string, path: string): string[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value)) throw new Error(`${path}.${key} must be a YAML array`);
|
||||
return value.map((item, index) => {
|
||||
if (typeof item !== "string" || item.trim().length === 0) throw new Error(`${path}.${key}[${index}] must be a non-empty string`);
|
||||
return item.trim();
|
||||
});
|
||||
}
|
||||
|
||||
function stringRecordField(obj: Record<string, unknown>, path: string): Readonly<Record<string, string>> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
renderAgentRunGitopsFiles,
|
||||
type AgentRunArtifactService,
|
||||
} from "./agentrun-manifests";
|
||||
import { sha256Fingerprint } from "./platform-infra-ops-library";
|
||||
|
||||
export function agentRunHelp(): unknown {
|
||||
return {
|
||||
@@ -3669,7 +3669,7 @@ function readSecretSourceValue(spec: AgentRunLaneSpec, source: LaneSecretSource)
|
||||
redactedPath: sourceRef.startsWith("/") ? redactAbsoluteSecretPath(sourceRef) : `.state/secrets/${sourceRef}`,
|
||||
value,
|
||||
valueBytes: Buffer.byteLength(value, "utf8"),
|
||||
fingerprint: `sha256:${createHash("sha256").update(value).digest("hex")}`,
|
||||
fingerprint: sha256Fingerprint(value),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,18 @@ export function fingerprintValues(values: Record<string, string>, keys: string[]
|
||||
return `sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function sha256Hex(value: string): string {
|
||||
return createHash("sha256").update(value).digest("hex");
|
||||
}
|
||||
|
||||
export function sha256Fingerprint(value: string): string {
|
||||
return `sha256:${sha256Hex(value)}`;
|
||||
}
|
||||
|
||||
export function shortSha256Fingerprint(value: string, chars = 12): string {
|
||||
return sha256Hex(value).slice(0, chars);
|
||||
}
|
||||
|
||||
export function fingerprintEnvValues(values: Record<string, string>, keys: string[]): string {
|
||||
const material = keys
|
||||
.slice()
|
||||
@@ -178,14 +190,14 @@ export function recordField(obj: Record<string, unknown>, key: string, path: str
|
||||
export function stringField(obj: Record<string, unknown>, key: string, path: string): string {
|
||||
const value = obj[key];
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string`);
|
||||
return value;
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export function optionalStringField(obj: Record<string, unknown>, key: string, path: string): string | undefined {
|
||||
const value = obj[key];
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string when set`);
|
||||
return value;
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
export function booleanField(obj: Record<string, unknown>, key: string, path: string): boolean {
|
||||
@@ -200,6 +212,19 @@ export function numberField(obj: Record<string, unknown>, key: string, path: str
|
||||
return value;
|
||||
}
|
||||
|
||||
export function integerField(obj: Record<string, unknown>, key: string, path: string): number {
|
||||
const value = obj[key];
|
||||
if (!Number.isInteger(value)) throw new Error(`${path}.${key} must be an integer`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
export function optionalIntegerField(obj: Record<string, unknown>, key: string, path: string): number | undefined {
|
||||
const value = obj[key];
|
||||
if (value === undefined || value === null) return undefined;
|
||||
if (!Number.isInteger(value)) throw new Error(`${path}.${key} must be an integer when set`);
|
||||
return Number(value);
|
||||
}
|
||||
|
||||
export function arrayField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown>[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value)) throw new Error(`${path}.${key} must be an array`);
|
||||
@@ -212,6 +237,15 @@ export function stringListField(obj: Record<string, unknown>, key: string, path:
|
||||
return value as string[];
|
||||
}
|
||||
|
||||
export function stringArrayField(obj: Record<string, unknown>, key: string, path: string): string[] {
|
||||
const value = obj[key];
|
||||
if (!Array.isArray(value)) throw new Error(`${path}.${key} must be an array`);
|
||||
return value.map((item, index) => {
|
||||
if (typeof item !== "string" || item.trim().length === 0) throw new Error(`${path}.${key}[${index}] must be a non-empty string`);
|
||||
return item.trim();
|
||||
});
|
||||
}
|
||||
|
||||
export function yamlFieldLabel(configLabel: string, path: string, key: string): string {
|
||||
const prefix = path.length > 0 ? `${path}.` : "";
|
||||
return `${configLabel}.${prefix}${key}`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -6,6 +5,7 @@ import type { UniDeskConfig } from "./config";
|
||||
import { rootPath } from "./config";
|
||||
import type { RenderedCliResult } from "./output";
|
||||
import { applyPk01CaddyBlock, prepareFrpcSecret, renderFrpcManifest, type PublicServiceExposure, type PublicServiceTarget } from "./platform-infra-public-service";
|
||||
import { shortSha256Fingerprint } from "./platform-infra-ops-library";
|
||||
import {
|
||||
codexPoolSentinelSummary,
|
||||
codexPoolSentinelRuntimeImage,
|
||||
@@ -3793,7 +3793,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
}
|
||||
|
||||
function fingerprint(value: string): string {
|
||||
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
||||
return shortSha256Fingerprint(value);
|
||||
}
|
||||
|
||||
export function codexPoolSentinelProbeConfigFingerprint(input: {
|
||||
|
||||
Reference in New Issue
Block a user