🌳
pt0/deployF/k8sF/ctxF/klusterCtxF.mts
3import { appCfgCtx, type AppCfg, getAppCfg} from "./appCfgCtxF.mts";
4import * as _ from 'lodash-es'
6import { availActionsCtx, type ActionsH } from "../../ctxF/availActionsCtxF.mts";
9/** Common props for functions requiring cluster_name */
10export type WithClusterName = { cluster_name: string }
12/** Props for functions requiring cluster_name + app/resource name */
13export type WithClusterAndName = WithClusterName & { name: string }
15/** Props for functions requiring cluster_name + k8s resource */
16export type KubeResource = { metadata: { name: string, namespace?: string }, kind?: string, apiVersion?: string, spec?: Record<string, unknown> }
17export type WithClusterAndResource = WithClusterName & { resource: KubeResource }
19/** Per-cluster node ports config from nodeports/*.mjs */
20export type NodePortsCfg = {
21 nodeIpsExtHost?: string
22 tailscaleIp?: string
23 tsRelayIp?: string
24 openPortNoA?: import('../../../sharedF/portInOpenAAI.mts').OpenPortNoA
25 lanHostIpsH?: { clusterVip?: string; [hostname: string]: string | undefined }
26 [svcName: string]: { isCnPg?: boolean; ptNodePortNo?: number; needsKubePortFwd?: boolean } | string | { clusterVip?: string; [k: string]: string | undefined } | import('../../../sharedF/portInOpenAAI.mts').OpenPortNoA | undefined
29/** Per-cluster storage-class policy. Absent → PVCs use the cluster's default class. */
30export type StorClassGenFn = (props: { useM2?: boolean, pvcReplicaCnt?: number, [key: string]: any }) => { pvcStorClassName: string }
32/** Cluster infrastructure config - rarely changes per-app within a cluster */
33export type KlusterCfg = WithClusterName & {
34 dockreg_host?: string
35 dockregPoolA?: string[]
36 dockLanHost?: string
37 regcacheEnabled?: boolean
38 regcacheHost?: string
39 regcacheLanHost?: string
40 useZotOverDockreg?: boolean
41 nodeIpsExtHost?: string
42 lanNodeIp?: string
43 clusterVip?: string
44 klusterVipHostname?: string
45 k8sCloudName?: string
46 storClassGenFn?: StorClassGenFn
47 pvcReplicaCnt?: number
48 allNodePorts?: Record<string, NodePortsCfg>
49 klustCertsH?: Record<string, string | string[]>
50 domainNames?: string[]
51 cfApiKeySecretName?: string
52 extDnsPolicy?: string
53 kubeTlsInsecure?: boolean
54 extraActions?: ActionsH
55 accessByIp?: string
56 serverIp?: string
57 sshNodeHostname?: string
58 wcCertName?: string
59 /**
60 * Reachability caveat for this cluster. Mutually exclusive dual-purpose:
61 * - string: cluster is known DOWN (value = reason). Reads behave as "not found"; mutating ops throw instantly.
62 * - number: cluster is up but slow/flaky; used as the per-request timeout in seconds (overrides 20s default).
63 * - absent: healthy; requests capped at defaultKubeTimeoutSec.
64 */
65 reachability?: string | number
68export const klusterCtx = genContext<KlusterCfg>()
70/** Import-time registry of cluster configs (populated by each klustersF/<name>/common.mjs). Enables
71 * cross-cluster lookups (reachability, LAN-rewrite) in entrypoints that never enter a klusterCtx,
72 * e.g. zhong jobs that only import an ethStatusCfg. */
73const klusterCfgRegistry = new Map<string, KlusterCfg>()
75export const registerKlusterCfg = (kcfg: KlusterCfg) => {
76 const prev = klusterCfgRegistry.get(kcfg.cluster_name)
77 klusterCfgRegistry.set(kcfg.cluster_name, prev ? {...prev, ...kcfg} : kcfg)
80/** Resolve a registry hostname alias to its canonical external host. A registry is one credential
81 * identity; LAN/derived hostnames are network aliases. Without this, `getCreds` mints a second
82 * cred file per alias (different password) and registry auth (htpasswd) no longer matches. */
83export const getDockregCanonicalHost = (host: string): string => {
84 const ctx = klusterCtx.getStore() ?? getAppCfg()
85 if (ctx?.dockLanHost === host && ctx.dockreg_host) return ctx.dockreg_host
86 for (const cfg of klusterCfgRegistry.values()) if (cfg.dockLanHost === host && cfg.dockreg_host) return cfg.dockreg_host
87 return host
90export const getKlusterCtx = (mergeH ?: object) => {
91 const store = klusterCtx.getStore() ?? getAppCfg() ?? {}
92 return mergeConcatA({...store, action: getAction()}, mergeH ?? {})
95export const getReqKlusterCtx = (): KlusterCfg => {
96 const store = klusterCtx.getStore()
98 return store
101/**
102 * Lookup a specific cluster's config by name. Use this when operating on a cluster
103 * that may differ from the currently-active one (e.g. cross-cluster db ops).
104 * Resolution order:
105 * 1. `allKlusters` registry on appCfg (set by `enterKlusterCtxs` or `js3DevAppCfg`)
106 * 2. `klusterCtx` if its `cluster_name` matches the request
107 * 3. `appCfg` if its `cluster_name` matches the request
108 * 4. `klusterCfgRegistry` (populated at import time by klustersF/<name>/common.mjs)
109 * 5. `{}` (so destructuring is safe; caller should assertDefined on required fields)
110 */
111export const getCurKlusterCfg = (cluster_name: string): Partial<KlusterCfg> => {
112 assertDefined(cluster_name)
113 const registered = _.get(getAppCfg(), ['allKlusters', cluster_name]) as KlusterCfg | undefined
114 if (registered) return registered
115 const ctxStore = klusterCtx.getStore()
116 if (ctxStore?.cluster_name === cluster_name) return ctxStore
117 const appCfg = getAppCfg()
118 if (appCfg?.cluster_name === cluster_name) return appCfg as Partial<KlusterCfg>
119 const imported = klusterCfgRegistry.get(cluster_name)
120 if (imported) return imported
121 return {}
124/** Get node ports config for a cluster, checking both klusterCtx and appCfgCtx */
125export const getNodePortsCfg = (cluster_name?: string): NodePortsCfg | undefined => {
126 if (!cluster_name) return undefined
127 const ctx = klusterCtx.getStore()
128 const appCfg = getAppCfg()
129 return ctx?.allNodePorts?.[cluster_name] ?? appCfg?.allNodePorts?.[cluster_name]
132export const enterKlusterCtxs = (klusterCfg: KlusterCfg, opts?: {importMetaUrl?: string}) => {
133 klusterCtx.enterWith(klusterCfg)
134 appCfgCtx.enterWith({
135 ...klusterCfg,
136 allKlusters: {[klusterCfg.cluster_name]: klusterCfg},
137 importMetaUrl: opts?.importMetaUrl as AppCfg['importMetaUrl'],
138 })
139 if (klusterCfg.extraActions) {
140 availActionsCtx.enterWith({...availActionsCtx.getStore(), ...klusterCfg.extraActions})
141 }