216 lines
9.0 KiB
TypeScript
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);
|
|
}
|