Files
pikasTech-HWLAB/scripts/src/runtime-lane.ts
T
2026-06-27 13:33:45 +00:00

216 lines
9.0 KiB
TypeScript

type RuntimeLaneDefaults = {
defaultNodeId?: string;
defaultGitopsRoot?: string;
defaultBranch?: string;
defaultGitopsBranch?: string;
defaultCatalogPath?: string;
defaultRuntimeEndpoint?: string;
defaultWebEndpoint?: string;
defaultV02RuntimeEndpoint?: string;
defaultSourceRepo?: string;
};
export type GitOpsRenderArgs = {
lane: string;
nodeId?: string;
outDir: string;
gitopsRoot?: string;
sourceBranch: string;
gitopsBranch: string;
catalogPath: string;
imageTagMode: string;
runtimeEndpoint: string;
webEndpoint: string;
sourceRepo: string;
[key: string]: unknown;
};
export type RuntimeLaneConfig = {
node?: string;
sourceBranch?: string;
gitopsBranch?: string;
artifactCatalog?: string;
runtimePath?: string;
imageTagMode?: string;
endpoint?: string;
externalPostgres?: {
enabled?: boolean;
serviceName?: string;
endpointAddress?: string;
port?: number;
};
publicEndpoints?: {
frontend?: string;
api?: string;
[key: string]: string | undefined;
};
observability?: {
traceExplorerUrlTemplate?: string;
};
sourceRepo?: string;
};
export type NodeConfig = {
gitopsRoot?: string;
sourceRepo?: string;
publicHost?: string;
};
export type RuntimeLaneMirrorSpec = {
lane: string;
sourceBranch: string;
gitopsBranch: string;
artifactCatalog: string;
runtimePath: string;
};
export type DeployConfig = {
nodes?: Record<string, NodeConfig | undefined>;
lanes?: Record<string, RuntimeLaneConfig | undefined>;
};
const defaultNodeId = "node";
const defaultGitopsRoot = "deploy/gitops/node";
const defaultPublicHost = "74.48.78.17";
export function isRuntimeLane(lane: unknown): lane is string {
return /^v[0-9]{2,}$/u.test(String(lane ?? ""));
}
export function runtimeLaneMinor(lane: string): number {
invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`);
return Number.parseInt(lane.slice(1), 10);
}
export function versionNameForRuntimeLane(lane: string): string {
return `v0.${runtimeLaneMinor(lane)}`;
}
export function namespaceNameForRuntimeLane(lane: string): string {
invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`);
return `hwlab-${lane}`;
}
export function runtimePathForRuntimeLane(lane: string): string {
invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`);
return `runtime-${lane}`;
}
export function nodeConfig(deploy: DeployConfig, nodeId: string): NodeConfig {
const config = deploy.nodes?.[nodeId];
return isObject(config) ? config : {};
}
export function nodeIdForRuntimeLane(deploy: DeployConfig, lane: string, defaults: RuntimeLaneDefaults = {}): string {
const laneConfig = runtimeLaneConfig(deploy, lane);
return laneConfig.node ?? defaults.defaultNodeId ?? defaultNodeId;
}
export function gitopsRootForNode(deploy: DeployConfig, nodeId: string, defaults: RuntimeLaneDefaults = {}): string {
const config = nodeConfig(deploy, nodeId);
return normalizeRepoPath(config.gitopsRoot ?? defaults.defaultGitopsRoot ?? defaultGitopsRoot);
}
export function runtimeGitopsPathForRuntimeLane(deploy: DeployConfig, lane: string, defaults: RuntimeLaneDefaults = {}): string {
const laneConfig = runtimeLaneConfig(deploy, lane);
const runtimePath = normalizeRepoPath(laneConfig.runtimePath ?? runtimePathForRuntimeLane(lane));
if (runtimePath.startsWith("deploy/")) return runtimePath;
return joinRepoPath(gitopsRootForNode(deploy, nodeIdForRuntimeLane(deploy, lane, defaults), defaults), runtimePath);
}
export function defaultPortForRuntimeLane(lane: string): number {
return 17666 + runtimeLaneMinor(lane) * 1000;
}
export function publicHostLabel(host = defaultPublicHost): string {
return host.replaceAll(".", "-");
}
export function defaultEndpointForRuntimeLane(lane: string, options: { host?: string; v02Endpoint?: string } = {}): string {
if (lane === "v02" && options.v02Endpoint) return options.v02Endpoint;
return `https://hwlab-${lane}.${publicHostLabel(options.host ?? defaultPublicHost)}.nip.io`;
}
export function runtimeLaneConfig(deploy: DeployConfig, lane: string): RuntimeLaneConfig {
invariant(isRuntimeLane(lane), `runtime lane must look like v02/v03, got ${lane}`);
const config = deploy.lanes?.[lane];
invariant(isObject(config), `deploy.lanes.${lane} is required`);
return config;
}
export function applyRuntimeLaneDeployConfig(args: GitOpsRenderArgs, deploy: DeployConfig, defaults: RuntimeLaneDefaults = {}): GitOpsRenderArgs {
if (!isRuntimeLane(args.lane)) return args;
const result: GitOpsRenderArgs = { ...args };
const laneConfig = runtimeLaneConfig(deploy, args.lane);
const configuredNodeId = nodeIdForRuntimeLane(deploy, args.lane, defaults);
const rootNodeId = nodeIdFromGitopsRoot(result.gitopsRoot);
const nodeId = result.nodeId && result.nodeId !== defaultNodeId ? result.nodeId : rootNodeId ?? configuredNodeId;
const node = nodeConfig(deploy, nodeId);
const versionName = versionNameForRuntimeLane(args.lane);
const defaultEndpointOptions: { host?: string; v02Endpoint?: string } = {};
if (node.publicHost) defaultEndpointOptions.host = node.publicHost;
if (defaults.defaultV02RuntimeEndpoint) defaultEndpointOptions.v02Endpoint = defaults.defaultV02RuntimeEndpoint;
const defaultEndpoint = defaultEndpointForRuntimeLane(args.lane, defaultEndpointOptions);
const configuredEndpoint = laneConfig.endpoint ?? defaultEndpoint;
const configuredWebEndpoint = laneConfig.publicEndpoints?.frontend ?? configuredEndpoint;
const configuredRuntimeEndpoint = laneConfig.publicEndpoints?.api ?? configuredEndpoint;
const defaultBranch = defaults.defaultBranch ?? "node";
const defaultGitopsBranch = defaults.defaultGitopsBranch ?? "node-gitops";
const defaultCatalogPath = defaults.defaultCatalogPath ?? "deploy/artifact-catalog.dev.json";
const defaultRuntimeEndpoint = defaults.defaultRuntimeEndpoint ?? "http://74.48.78.17:17667";
const defaultWebEndpoint = defaults.defaultWebEndpoint ?? "http://74.48.78.17:17666";
const defaultSourceRepo = defaults.defaultSourceRepo ?? "git@github.com:pikasTech/HWLAB.git";
const defaultCatalog = `deploy/artifact-catalog.${args.lane}.json`;
if (!result.nodeId || result.nodeId === defaultNodeId) result.nodeId = nodeId;
if (!result.gitopsRoot || result.gitopsRoot === (defaults.defaultGitopsRoot ?? defaultGitopsRoot)) result.gitopsRoot = gitopsRootForNode(deploy, nodeId, defaults);
if (result.sourceBranch === defaultBranch || result.sourceBranch === versionName) result.sourceBranch = laneConfig.sourceBranch ?? versionName;
if (result.gitopsBranch === defaultGitopsBranch || result.gitopsBranch === `${versionName}-gitops`) result.gitopsBranch = laneConfig.gitopsBranch ?? `${versionName}-gitops`;
if (result.catalogPath === defaultCatalogPath || result.catalogPath === defaultCatalog) result.catalogPath = laneConfig.artifactCatalog ?? defaultCatalog;
if (result.imageTagMode === "full") result.imageTagMode = laneConfig.imageTagMode ?? result.imageTagMode;
if (result.runtimeEndpoint === defaultRuntimeEndpoint || result.runtimeEndpoint === defaultEndpoint) result.runtimeEndpoint = configuredRuntimeEndpoint;
if (result.webEndpoint === defaultWebEndpoint || result.webEndpoint === defaultEndpoint) result.webEndpoint = configuredWebEndpoint;
if (result.sourceRepo === defaultSourceRepo) result.sourceRepo = laneConfig.sourceRepo ?? node.sourceRepo ?? result.sourceRepo;
return result;
}
function nodeIdFromGitopsRoot(gitopsRoot: unknown): string | undefined {
if (typeof gitopsRoot !== "string") return undefined;
const normalized = normalizeRepoPath(gitopsRoot);
const match = normalized.match(/(?:^|\/)deploy\/gitops\/node\/([^/]+)$/u);
return match?.[1] ? match[1].toUpperCase() : undefined;
}
export function runtimeLaneMirrorSpecs(deploy: DeployConfig, defaults: RuntimeLaneDefaults = {}): RuntimeLaneMirrorSpec[] {
return Object.entries(deploy.lanes ?? {})
.filter(([lane, config]) => isRuntimeLane(lane) && isObject(config))
.sort(([left], [right]) => runtimeLaneMinor(left) - runtimeLaneMinor(right))
.map(([lane, config]) => {
const laneConfig = config as RuntimeLaneConfig;
const versionName = versionNameForRuntimeLane(lane);
return {
lane,
sourceBranch: laneConfig.sourceBranch ?? versionName,
gitopsBranch: laneConfig.gitopsBranch ?? `${versionName}-gitops`,
artifactCatalog: laneConfig.artifactCatalog ?? `deploy/artifact-catalog.${lane}.json`,
runtimePath: runtimeGitopsPathForRuntimeLane(deploy, lane, defaults),
};
});
}
function normalizeRepoPath(value: string): string {
return value.replaceAll("\\\\", "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
}
function joinRepoPath(left: string, right: string): string {
return `${normalizeRepoPath(left)}/${normalizeRepoPath(right).replace(/^\/+/, "")}`;
}
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function invariant(condition: unknown, message: string): asserts condition {
if (!condition) throw new Error(message);
}