@intentius/chant-lexicon-k8s 0.1.0 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.json +42 -39
- package/dist/manifest.json +1 -1
- package/dist/meta.json +121 -0
- package/dist/rules/k8s-helpers.ts +39 -0
- package/dist/rules/wk8401.ts +98 -0
- package/dist/rules/wk8402.ts +43 -0
- package/dist/rules/wk8403.ts +60 -0
- package/dist/types/index.d.ts +30 -0
- package/package.json +11 -7
- package/src/actions/actions.test.ts +1 -1
- package/src/codegen/generate-cli.ts +1 -1
- package/src/codegen/generate.ts +22 -0
- package/src/codegen/naming.test.ts +1 -1
- package/src/codegen/package.ts +2 -5
- package/src/codegen/snapshot.test.ts +1 -1
- package/src/codegen/typecheck.test.ts +1 -1
- package/src/composites/cockroachdb-region-stack.ts +553 -0
- package/src/composites/composites.test.ts +4 -4
- package/src/composites/index.ts +8 -0
- package/src/composites/ray-cluster.ts +590 -0
- package/src/composites/ray-job.ts +235 -0
- package/src/composites/ray-service.ts +271 -0
- package/src/coverage.test.ts +1 -1
- package/src/crd/crd-sources.ts +29 -0
- package/src/crd/loader.ts +13 -21
- package/src/crd/parser.test.ts +1 -1
- package/src/crd/parser.ts +17 -12
- package/src/default-labels.test.ts +1 -1
- package/src/generated/index.d.ts +30 -0
- package/src/generated/index.ts +13 -0
- package/src/generated/lexicon-k8s.json +121 -0
- package/src/import/generator.test.ts +1 -1
- package/src/import/parser.test.ts +1 -1
- package/src/import/roundtrip.test.ts +1 -1
- package/src/index.ts +4 -0
- package/src/lint/post-synth/k8s-helpers.test.ts +1 -1
- package/src/lint/post-synth/k8s-helpers.ts +39 -0
- package/src/lint/post-synth/post-synth.test.ts +149 -1
- package/src/lint/post-synth/wk8401.ts +98 -0
- package/src/lint/post-synth/wk8402.ts +43 -0
- package/src/lint/post-synth/wk8403.ts +60 -0
- package/src/lint/rules/rules.test.ts +1 -1
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/hover.test.ts +1 -1
- package/src/package-cli.ts +1 -1
- package/src/plugin.test.ts +3 -3
- package/src/plugin.ts +7 -9
- package/src/serializer.test.ts +1 -1
- package/src/serializer.ts +2 -0
- package/src/skills/chant-k8s-ray.md +252 -0
- package/src/spec/fetch.test.ts +1 -1
- package/src/spec/parse.test.ts +1 -1
- package/src/validate-cli.ts +1 -1
- package/src/validate.test.ts +1 -1
package/src/codegen/package.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* with K8s-specific manifest building and skill collection.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createRequire } from "module";
|
|
7
6
|
import { readFileSync } from "fs";
|
|
8
|
-
|
|
7
|
+
import { k8sPlugin } from "../plugin";
|
|
9
8
|
import { join, dirname } from "path";
|
|
10
9
|
import { fileURLToPath } from "url";
|
|
11
10
|
import {
|
|
@@ -43,9 +42,7 @@ export async function packageLexicon(opts: PackageOptions = {}): Promise<Package
|
|
|
43
42
|
|
|
44
43
|
srcDir: pkgDir,
|
|
45
44
|
|
|
46
|
-
collectSkills: () => {
|
|
47
|
-
const { k8sPlugin } = require("../plugin");
|
|
48
|
-
const skillDefs = k8sPlugin.skills?.() ?? [];
|
|
45
|
+
collectSkills: () => { const skillDefs = k8sPlugin.skills?.() ?? [];
|
|
49
46
|
return collectSkills(skillDefs);
|
|
50
47
|
},
|
|
51
48
|
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CockroachDbRegionStack composite — all K8s resources for one CockroachDB region.
|
|
3
|
+
*
|
|
4
|
+
* Collapses the 8-file per-region K8s pattern into a single composite call:
|
|
5
|
+
* namespace, storage, CockroachDB StatefulSet, External Secrets, GCE Ingress,
|
|
6
|
+
* ExternalDNS, Cloud Armor BackendConfig, TLS cert, and optional Prometheus monitoring.
|
|
7
|
+
*
|
|
8
|
+
* @gke Requires: External Secrets Operator (ESO), ExternalDNS (via GkeExternalDnsAgent).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
12
|
+
import { createResource } from "@intentius/chant/runtime";
|
|
13
|
+
import {
|
|
14
|
+
NetworkPolicy,
|
|
15
|
+
Deployment,
|
|
16
|
+
Service,
|
|
17
|
+
ConfigMap,
|
|
18
|
+
} from "../generated";
|
|
19
|
+
import { NamespaceEnv } from "./namespace-env";
|
|
20
|
+
import { GcePdStorageClass } from "./gce-pd-storage-class";
|
|
21
|
+
import { CockroachDbCluster } from "./cockroachdb-cluster";
|
|
22
|
+
import { GceIngress } from "./gce-ingress";
|
|
23
|
+
import { GkeExternalDnsAgent } from "./gke-external-dns-agent";
|
|
24
|
+
|
|
25
|
+
// ── External CRDs (not in main generated index) ──────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const ClusterSecretStore = createResource("K8s::ExternalSecrets::ClusterSecretStore", "k8s", {});
|
|
28
|
+
const ExternalSecret = createResource("K8s::ExternalSecrets::ExternalSecret", "k8s", {});
|
|
29
|
+
const BackendConfig = createResource("K8s::GKE::BackendConfig", "k8s", {});
|
|
30
|
+
const ManagedCertificate = createResource("K8s::NetworkingGKE::ManagedCertificate", "k8s", {});
|
|
31
|
+
const FrontendConfig = createResource("K8s::NetworkingGKEBeta::FrontendConfig", "k8s", {});
|
|
32
|
+
|
|
33
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
export interface CockroachDbRegionCockroachConfig {
|
|
36
|
+
/** Cluster name (default: "cockroachdb"). */
|
|
37
|
+
name?: string;
|
|
38
|
+
/** StatefulSet replicas (default: 3). */
|
|
39
|
+
replicas?: number;
|
|
40
|
+
/** CockroachDB image (default: "cockroachdb/cockroach:v24.3.0"). */
|
|
41
|
+
image?: string;
|
|
42
|
+
/** PVC storage size per node (default: "100Gi"). */
|
|
43
|
+
storageSize?: string;
|
|
44
|
+
/** CPU limit per pod (default: "2"). */
|
|
45
|
+
cpuLimit?: string;
|
|
46
|
+
/** Memory limit per pod (default: "8Gi"). */
|
|
47
|
+
memoryLimit?: string;
|
|
48
|
+
/** Locality flag (e.g. "cloud=gcp,region=us-east4"). */
|
|
49
|
+
locality?: string;
|
|
50
|
+
/** Join addresses for multi-region cluster. */
|
|
51
|
+
joinAddresses?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Skip the cockroach init Job (set true for secondary regions).
|
|
54
|
+
* Only the first region should run cockroach init.
|
|
55
|
+
*/
|
|
56
|
+
skipInit?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Mount the client certs secret into pods (needed for multi-region init via kubectl exec).
|
|
59
|
+
*/
|
|
60
|
+
mountClientCerts?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Extra DNS names to include in node cert SANs (e.g. cross-region ExternalDNS names).
|
|
63
|
+
*/
|
|
64
|
+
extraCertNodeAddresses?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Domain for cross-cluster advertise address (e.g. "east.crdb.internal").
|
|
67
|
+
* When set, pods advertise "${HOSTNAME}.${advertiseHostDomain}" for cross-cluster gossip.
|
|
68
|
+
*/
|
|
69
|
+
advertiseHostDomain?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface CockroachDbRegionTlsConfig {
|
|
73
|
+
/**
|
|
74
|
+
* GCP Secret Manager secret names to sync into K8s Secrets via External Secrets Operator.
|
|
75
|
+
*/
|
|
76
|
+
gcpSecretNames: {
|
|
77
|
+
/** CA cert secret name in GCP Secret Manager. */
|
|
78
|
+
ca: string;
|
|
79
|
+
/** Node cert secret name. */
|
|
80
|
+
nodeCrt: string;
|
|
81
|
+
/** Node cert key secret name. */
|
|
82
|
+
nodeKey: string;
|
|
83
|
+
/** Client root cert secret name. */
|
|
84
|
+
clientRootCrt: string;
|
|
85
|
+
/** Client root key secret name. */
|
|
86
|
+
clientRootKey: string;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CockroachDbRegionStackConfig {
|
|
91
|
+
/**
|
|
92
|
+
* Short region identifier used in resource names and labels (e.g. "east").
|
|
93
|
+
* Used to distinguish resources across regions.
|
|
94
|
+
*/
|
|
95
|
+
region: string;
|
|
96
|
+
/** K8s namespace for all namespaced resources (e.g. "crdb-east"). */
|
|
97
|
+
namespace: string;
|
|
98
|
+
/** Public UI domain for this region (e.g. "east.crdb.example.com"). */
|
|
99
|
+
domain: string;
|
|
100
|
+
/** Cross-cluster ExternalDNS domain (e.g. "east.crdb.internal"). */
|
|
101
|
+
internalDomain: string;
|
|
102
|
+
/** Root public domain for ExternalDNS domain filter (e.g. "crdb.example.com"). */
|
|
103
|
+
publicRootDomain: string;
|
|
104
|
+
|
|
105
|
+
/** GCP project ID — used in ClusterSecretStore Workload Identity config. */
|
|
106
|
+
projectId: string;
|
|
107
|
+
/** GKE cluster name — used in ClusterSecretStore Workload Identity config. */
|
|
108
|
+
clusterName: string;
|
|
109
|
+
/** GKE cluster region (e.g. "us-east4") — used in ClusterSecretStore config. */
|
|
110
|
+
clusterRegion: string;
|
|
111
|
+
/** GCP service account email for CRDB Workload Identity annotation on the K8s SA. */
|
|
112
|
+
crdbGsaEmail: string;
|
|
113
|
+
/** GCP service account email for ExternalDNS Workload Identity. */
|
|
114
|
+
externalDnsGsaEmail: string;
|
|
115
|
+
|
|
116
|
+
/** CockroachDB cluster configuration. */
|
|
117
|
+
cockroachdb: CockroachDbRegionCockroachConfig;
|
|
118
|
+
/** TLS certificate sync config (External Secrets Operator). */
|
|
119
|
+
tls: CockroachDbRegionTlsConfig;
|
|
120
|
+
|
|
121
|
+
/** Namespace resource quotas. */
|
|
122
|
+
quota?: {
|
|
123
|
+
/** Total CPU limit quota (e.g. "8"). */
|
|
124
|
+
cpu?: string;
|
|
125
|
+
/** Total memory limit quota (e.g. "20Gi"). */
|
|
126
|
+
memory?: string;
|
|
127
|
+
/** Maximum pods. */
|
|
128
|
+
maxPods?: number;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* CIDRs that may send traffic to CockroachDB ports (26257 + 8080).
|
|
133
|
+
* Used in the allow-cockroachdb NetworkPolicy.
|
|
134
|
+
*/
|
|
135
|
+
allowCidrs?: string[];
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Cloud Armor WAF configuration.
|
|
139
|
+
* When set, a BackendConfig is created attaching the policy to the public service.
|
|
140
|
+
*/
|
|
141
|
+
cloudArmor?: {
|
|
142
|
+
/** Cloud Armor security policy name. */
|
|
143
|
+
policyName: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Emit Prometheus ConfigMap + Deployment + Service for CockroachDB metrics.
|
|
148
|
+
* Uses a lightweight Prometheus scraping `/_status/vars` (default: false).
|
|
149
|
+
*/
|
|
150
|
+
monitoring?: boolean;
|
|
151
|
+
|
|
152
|
+
/** Additional labels for all resources. */
|
|
153
|
+
labels?: Record<string, string>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create a CockroachDbRegionStack composite — returns all K8s resources for one
|
|
158
|
+
* CockroachDB region in a single call.
|
|
159
|
+
*
|
|
160
|
+
* Replaces: namespace.ts, storage.ts, cockroachdb.ts, external-secrets.ts,
|
|
161
|
+
* ingress.ts, tls.ts, backend-config.ts, monitoring.ts (8 files → 1 call).
|
|
162
|
+
*
|
|
163
|
+
* @gke
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* import { CockroachDbRegionStack } from "@intentius/chant-lexicon-k8s";
|
|
167
|
+
*
|
|
168
|
+
* export const east = CockroachDbRegionStack({
|
|
169
|
+
* region: "east",
|
|
170
|
+
* namespace: "crdb-east",
|
|
171
|
+
* domain: `east.${CRDB_DOMAIN}`,
|
|
172
|
+
* internalDomain: `east.${INTERNAL_DOMAIN}`,
|
|
173
|
+
* publicRootDomain: CRDB_DOMAIN,
|
|
174
|
+
* projectId: GCP_PROJECT_ID,
|
|
175
|
+
* clusterName: "gke-crdb-east",
|
|
176
|
+
* clusterRegion: "us-east4",
|
|
177
|
+
* crdbGsaEmail: `gke-crdb-east-crdb@${GCP_PROJECT_ID}.iam.gserviceaccount.com`,
|
|
178
|
+
* externalDnsGsaEmail: `gke-crdb-east-dns@${GCP_PROJECT_ID}.iam.gserviceaccount.com`,
|
|
179
|
+
* cockroachdb: {
|
|
180
|
+
* locality: "cloud=gcp,region=us-east4",
|
|
181
|
+
* joinAddresses: [...],
|
|
182
|
+
* advertiseHostDomain: `east.${INTERNAL_DOMAIN}`,
|
|
183
|
+
* skipInit: false,
|
|
184
|
+
* mountClientCerts: true,
|
|
185
|
+
* },
|
|
186
|
+
* tls: {
|
|
187
|
+
* gcpSecretNames: {
|
|
188
|
+
* ca: "crdb-ca-crt", nodeCrt: "crdb-node-crt", nodeKey: "crdb-node-key",
|
|
189
|
+
* clientRootCrt: "crdb-client-root-crt", clientRootKey: "crdb-client-root-key",
|
|
190
|
+
* },
|
|
191
|
+
* },
|
|
192
|
+
* allowCidrs: ALL_CIDRS,
|
|
193
|
+
* cloudArmor: { policyName: "crdb-ui-waf" },
|
|
194
|
+
* monitoring: true,
|
|
195
|
+
* });
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
export const CockroachDbRegionStack = Composite<CockroachDbRegionStackConfig>((props) => {
|
|
199
|
+
const {
|
|
200
|
+
region,
|
|
201
|
+
namespace: ns,
|
|
202
|
+
domain,
|
|
203
|
+
internalDomain,
|
|
204
|
+
publicRootDomain,
|
|
205
|
+
projectId,
|
|
206
|
+
clusterName,
|
|
207
|
+
clusterRegion,
|
|
208
|
+
crdbGsaEmail,
|
|
209
|
+
externalDnsGsaEmail,
|
|
210
|
+
cockroachdb: crdbProps,
|
|
211
|
+
tls,
|
|
212
|
+
quota,
|
|
213
|
+
allowCidrs = [],
|
|
214
|
+
cloudArmor,
|
|
215
|
+
monitoring = false,
|
|
216
|
+
labels: extraLabels = {},
|
|
217
|
+
} = props;
|
|
218
|
+
|
|
219
|
+
const crdbName = crdbProps.name ?? "cockroachdb";
|
|
220
|
+
|
|
221
|
+
const commonLabels: Record<string, string> = {
|
|
222
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
223
|
+
"app.kubernetes.io/part-of": `cockroachdb-multi-region`,
|
|
224
|
+
"app.kubernetes.io/instance": region,
|
|
225
|
+
...extraLabels,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ── Namespace + Quotas ─────────────────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
const nsResult = NamespaceEnv({
|
|
231
|
+
name: ns,
|
|
232
|
+
...(quota?.cpu && { cpuQuota: quota.cpu }),
|
|
233
|
+
...(quota?.memory && { memoryQuota: quota.memory }),
|
|
234
|
+
...(quota?.maxPods !== undefined && { maxPods: quota.maxPods }),
|
|
235
|
+
defaultCpuRequest: "250m",
|
|
236
|
+
defaultMemoryRequest: "512Mi",
|
|
237
|
+
defaultCpuLimit: "1",
|
|
238
|
+
defaultMemoryLimit: "4Gi",
|
|
239
|
+
defaultDenyIngress: true,
|
|
240
|
+
labels: {
|
|
241
|
+
"pod-security.kubernetes.io/enforce": "baseline",
|
|
242
|
+
"pod-security.kubernetes.io/enforce-version": "latest",
|
|
243
|
+
"pod-security.kubernetes.io/warn": "restricted",
|
|
244
|
+
"pod-security.kubernetes.io/audit": "restricted",
|
|
245
|
+
...commonLabels,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Allow CockroachDB inter-node (26257) + HTTP UI (8080) from all region CIDRs.
|
|
250
|
+
const crdbNetworkPolicy = new NetworkPolicy(mergeDefaults({
|
|
251
|
+
metadata: {
|
|
252
|
+
name: "allow-cockroachdb",
|
|
253
|
+
namespace: ns,
|
|
254
|
+
labels: commonLabels,
|
|
255
|
+
},
|
|
256
|
+
spec: {
|
|
257
|
+
podSelector: { matchLabels: { "app.kubernetes.io/name": crdbName } },
|
|
258
|
+
policyTypes: ["Ingress"],
|
|
259
|
+
ingress: allowCidrs.length > 0
|
|
260
|
+
? [{
|
|
261
|
+
from: allowCidrs.map((cidr) => ({ ipBlock: { cidr } })),
|
|
262
|
+
ports: [
|
|
263
|
+
{ protocol: "TCP", port: 26257 },
|
|
264
|
+
{ protocol: "TCP", port: 8080 },
|
|
265
|
+
],
|
|
266
|
+
}]
|
|
267
|
+
: [],
|
|
268
|
+
},
|
|
269
|
+
}, {}));
|
|
270
|
+
|
|
271
|
+
// ── Storage ────────────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
const { storageClass } = GcePdStorageClass({
|
|
274
|
+
name: "pd-ssd",
|
|
275
|
+
type: "pd-ssd",
|
|
276
|
+
labels: commonLabels,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ── CockroachDB Cluster ────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
const crdb = CockroachDbCluster({
|
|
282
|
+
name: crdbName,
|
|
283
|
+
namespace: ns,
|
|
284
|
+
replicas: crdbProps.replicas ?? 3,
|
|
285
|
+
image: crdbProps.image,
|
|
286
|
+
storageSize: crdbProps.storageSize,
|
|
287
|
+
storageClassName: "pd-ssd",
|
|
288
|
+
cpuLimit: crdbProps.cpuLimit,
|
|
289
|
+
memoryLimit: crdbProps.memoryLimit,
|
|
290
|
+
locality: crdbProps.locality,
|
|
291
|
+
joinAddresses: crdbProps.joinAddresses,
|
|
292
|
+
secure: true,
|
|
293
|
+
skipCertGen: true,
|
|
294
|
+
skipInit: crdbProps.skipInit,
|
|
295
|
+
mountClientCerts: crdbProps.mountClientCerts,
|
|
296
|
+
advertiseHostDomain: crdbProps.advertiseHostDomain,
|
|
297
|
+
extraCertNodeAddresses: crdbProps.extraCertNodeAddresses,
|
|
298
|
+
labels: commonLabels,
|
|
299
|
+
defaults: {
|
|
300
|
+
serviceAccount: {
|
|
301
|
+
metadata: {
|
|
302
|
+
annotations: {
|
|
303
|
+
"iam.gke.io/gcp-service-account": crdbGsaEmail,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
publicService: {
|
|
308
|
+
metadata: {
|
|
309
|
+
annotations: {
|
|
310
|
+
"cloud.google.com/backend-config": `{"default":"${crdbName}-ui-backend"}`,
|
|
311
|
+
"cloud.google.com/app-protocols": `{"http":"HTTPS"}`,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
headlessService: {
|
|
316
|
+
metadata: {
|
|
317
|
+
annotations: {
|
|
318
|
+
"external-dns.alpha.kubernetes.io/hostname": internalDomain,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// ── External Secrets (TLS certs from GCP Secret Manager) ──────────────────────
|
|
326
|
+
|
|
327
|
+
const secretStoreName = "gcp-secret-manager";
|
|
328
|
+
|
|
329
|
+
const gcpSecretStore = new ClusterSecretStore({
|
|
330
|
+
metadata: { name: secretStoreName },
|
|
331
|
+
spec: {
|
|
332
|
+
provider: {
|
|
333
|
+
gcpsm: {
|
|
334
|
+
projectID: projectId,
|
|
335
|
+
auth: {
|
|
336
|
+
workloadIdentity: {
|
|
337
|
+
clusterLocation: clusterRegion,
|
|
338
|
+
clusterName,
|
|
339
|
+
serviceAccountRef: { name: "external-secrets-sa", namespace: "kube-system" },
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const nodeCertsSecret = new ExternalSecret({
|
|
348
|
+
metadata: { name: `${crdbName}-node-certs-eso`, namespace: ns },
|
|
349
|
+
spec: {
|
|
350
|
+
refreshInterval: "1h",
|
|
351
|
+
secretStoreRef: { name: secretStoreName, kind: "ClusterSecretStore" },
|
|
352
|
+
target: { name: `${crdbName}-node-certs`, creationPolicy: "Owner" },
|
|
353
|
+
data: [
|
|
354
|
+
{ secretKey: "ca.crt", remoteRef: { key: tls.gcpSecretNames.ca } },
|
|
355
|
+
{ secretKey: "node.crt", remoteRef: { key: tls.gcpSecretNames.nodeCrt } },
|
|
356
|
+
{ secretKey: "node.key", remoteRef: { key: tls.gcpSecretNames.nodeKey } },
|
|
357
|
+
],
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const clientCertsSecret = new ExternalSecret({
|
|
362
|
+
metadata: { name: `${crdbName}-client-certs-eso`, namespace: ns },
|
|
363
|
+
spec: {
|
|
364
|
+
refreshInterval: "1h",
|
|
365
|
+
secretStoreRef: { name: secretStoreName, kind: "ClusterSecretStore" },
|
|
366
|
+
target: { name: `${crdbName}-client-certs`, creationPolicy: "Owner" },
|
|
367
|
+
data: [
|
|
368
|
+
{ secretKey: "ca.crt", remoteRef: { key: tls.gcpSecretNames.ca } },
|
|
369
|
+
{ secretKey: "client.root.crt", remoteRef: { key: tls.gcpSecretNames.clientRootCrt } },
|
|
370
|
+
{ secretKey: "client.root.key", remoteRef: { key: tls.gcpSecretNames.clientRootKey } },
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// ── TLS: Managed Certificate + FrontendConfig ─────────────────────────────────
|
|
376
|
+
|
|
377
|
+
const certName = `${crdbName}-ui-cert`;
|
|
378
|
+
const frontendName = `${crdbName}-ui-frontend`;
|
|
379
|
+
const backendName = `${crdbName}-ui-backend`;
|
|
380
|
+
|
|
381
|
+
const crdbManagedCert = new ManagedCertificate({
|
|
382
|
+
metadata: {
|
|
383
|
+
name: certName,
|
|
384
|
+
namespace: ns,
|
|
385
|
+
labels: commonLabels,
|
|
386
|
+
},
|
|
387
|
+
spec: { domains: [domain] },
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const crdbFrontendConfig = new FrontendConfig({
|
|
391
|
+
metadata: {
|
|
392
|
+
name: frontendName,
|
|
393
|
+
namespace: ns,
|
|
394
|
+
labels: commonLabels,
|
|
395
|
+
},
|
|
396
|
+
spec: {
|
|
397
|
+
redirectToHttps: {
|
|
398
|
+
enabled: true,
|
|
399
|
+
responseCodeName: "MOVED_PERMANENTLY_DEFAULT",
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// ── GCE Ingress ────────────────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
const { ingress: gceIngress } = GceIngress({
|
|
407
|
+
name: `${crdbName}-ui`,
|
|
408
|
+
hosts: [{
|
|
409
|
+
hostname: domain,
|
|
410
|
+
paths: [{ path: "/", serviceName: `${crdbName}-public`, servicePort: 8080 }],
|
|
411
|
+
}],
|
|
412
|
+
namespace: ns,
|
|
413
|
+
managedCertificate: certName,
|
|
414
|
+
frontendConfig: frontendName,
|
|
415
|
+
labels: commonLabels,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// ── ExternalDNS ────────────────────────────────────────────────────────────────
|
|
419
|
+
|
|
420
|
+
const dnsAgent = GkeExternalDnsAgent({
|
|
421
|
+
gcpServiceAccountEmail: externalDnsGsaEmail,
|
|
422
|
+
gcpProjectId: projectId,
|
|
423
|
+
domainFilters: [publicRootDomain, internalDomain.split(".").slice(1).join(".")],
|
|
424
|
+
txtOwnerId: clusterName,
|
|
425
|
+
source: ["service", "ingress"],
|
|
426
|
+
labels: commonLabels,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// ── Result object ─────────────────────────────────────────────────────────────
|
|
430
|
+
|
|
431
|
+
const result: Record<string, any> = {
|
|
432
|
+
// Namespace
|
|
433
|
+
...nsResult,
|
|
434
|
+
crdbNetworkPolicy,
|
|
435
|
+
|
|
436
|
+
// Storage
|
|
437
|
+
storageClass,
|
|
438
|
+
|
|
439
|
+
// CockroachDB
|
|
440
|
+
cockroachdbServiceAccount: crdb.serviceAccount,
|
|
441
|
+
cockroachdbRole: crdb.role,
|
|
442
|
+
cockroachdbRoleBinding: crdb.roleBinding,
|
|
443
|
+
cockroachdbClusterRole: crdb.clusterRole,
|
|
444
|
+
cockroachdbClusterRoleBinding: crdb.clusterRoleBinding,
|
|
445
|
+
cockroachdbPublicService: crdb.publicService,
|
|
446
|
+
cockroachdbHeadlessService: crdb.headlessService,
|
|
447
|
+
cockroachdbPdb: crdb.pdb,
|
|
448
|
+
cockroachdbStatefulSet: crdb.statefulSet,
|
|
449
|
+
...(crdb.initJob && { cockroachdbInitJob: crdb.initJob }),
|
|
450
|
+
|
|
451
|
+
// External Secrets
|
|
452
|
+
gcpSecretStore,
|
|
453
|
+
nodeCertsSecret,
|
|
454
|
+
clientCertsSecret,
|
|
455
|
+
|
|
456
|
+
// TLS
|
|
457
|
+
crdbManagedCert,
|
|
458
|
+
crdbFrontendConfig,
|
|
459
|
+
|
|
460
|
+
// Ingress + ExternalDNS
|
|
461
|
+
gceIngress,
|
|
462
|
+
dnsDeployment: dnsAgent.deployment,
|
|
463
|
+
dnsServiceAccount: dnsAgent.serviceAccount,
|
|
464
|
+
dnsClusterRole: dnsAgent.clusterRole,
|
|
465
|
+
dnsClusterRoleBinding: dnsAgent.clusterRoleBinding,
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Optional: Cloud Armor BackendConfig
|
|
469
|
+
if (cloudArmor) {
|
|
470
|
+
result.crdbBackendConfig = new BackendConfig({
|
|
471
|
+
metadata: {
|
|
472
|
+
name: backendName,
|
|
473
|
+
namespace: ns,
|
|
474
|
+
labels: commonLabels,
|
|
475
|
+
},
|
|
476
|
+
spec: {
|
|
477
|
+
securityPolicy: { name: cloudArmor.policyName },
|
|
478
|
+
healthCheck: { type: "HTTPS", requestPath: "/health", port: 8080 },
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Optional: Prometheus monitoring
|
|
484
|
+
if (monitoring) {
|
|
485
|
+
result.prometheusConfig = new ConfigMap({
|
|
486
|
+
metadata: { name: "prometheus-config", namespace: ns },
|
|
487
|
+
data: {
|
|
488
|
+
"prometheus.yml": [
|
|
489
|
+
`global:`,
|
|
490
|
+
` scrape_interval: 15s`,
|
|
491
|
+
`scrape_configs:`,
|
|
492
|
+
` - job_name: "cockroachdb"`,
|
|
493
|
+
` kubernetes_sd_configs:`,
|
|
494
|
+
` - role: pod`,
|
|
495
|
+
` namespaces:`,
|
|
496
|
+
` names: ["${ns}"]`,
|
|
497
|
+
` relabel_configs:`,
|
|
498
|
+
` - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]`,
|
|
499
|
+
` regex: "cockroachdb"`,
|
|
500
|
+
` action: keep`,
|
|
501
|
+
` - source_labels: [__meta_kubernetes_pod_ip]`,
|
|
502
|
+
` target_label: __address__`,
|
|
503
|
+
` replacement: "$1:8080"`,
|
|
504
|
+
` - source_labels: [__meta_kubernetes_pod_name]`,
|
|
505
|
+
` target_label: instance`,
|
|
506
|
+
` metrics_path: "/_status/vars"`,
|
|
507
|
+
].join("\n"),
|
|
508
|
+
},
|
|
509
|
+
} as Record<string, unknown>);
|
|
510
|
+
|
|
511
|
+
result.prometheusDeployment = new Deployment({
|
|
512
|
+
metadata: {
|
|
513
|
+
name: "prometheus",
|
|
514
|
+
namespace: ns,
|
|
515
|
+
labels: { "app.kubernetes.io/name": "prometheus", ...commonLabels },
|
|
516
|
+
},
|
|
517
|
+
spec: {
|
|
518
|
+
replicas: 1,
|
|
519
|
+
selector: { matchLabels: { "app.kubernetes.io/name": "prometheus" } },
|
|
520
|
+
template: {
|
|
521
|
+
metadata: { labels: { "app.kubernetes.io/name": "prometheus" } },
|
|
522
|
+
spec: {
|
|
523
|
+
containers: [{
|
|
524
|
+
name: "prometheus",
|
|
525
|
+
image: "prom/prometheus:v2.51.0",
|
|
526
|
+
args: [
|
|
527
|
+
"--config.file=/etc/prometheus/prometheus.yml",
|
|
528
|
+
"--storage.tsdb.retention.time=15d",
|
|
529
|
+
],
|
|
530
|
+
ports: [{ name: "http", containerPort: 9090 }],
|
|
531
|
+
resources: {
|
|
532
|
+
requests: { cpu: "500m", memory: "1Gi" },
|
|
533
|
+
limits: { cpu: "1", memory: "2Gi" },
|
|
534
|
+
},
|
|
535
|
+
volumeMounts: [{ name: "config", mountPath: "/etc/prometheus" }],
|
|
536
|
+
}],
|
|
537
|
+
volumes: [{ name: "config", configMap: { name: "prometheus-config" } }],
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
},
|
|
541
|
+
} as Record<string, unknown>);
|
|
542
|
+
|
|
543
|
+
result.prometheusService = new Service({
|
|
544
|
+
metadata: { name: "prometheus", namespace: ns },
|
|
545
|
+
spec: {
|
|
546
|
+
selector: { "app.kubernetes.io/name": "prometheus" },
|
|
547
|
+
ports: [{ name: "http", port: 9090, targetPort: "http" }],
|
|
548
|
+
},
|
|
549
|
+
} as Record<string, unknown>);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return result;
|
|
553
|
+
}, "CockroachDbRegionStack");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, test, expect,
|
|
1
|
+
import { describe, test, expect, vi } from "vitest";
|
|
2
2
|
import { isCompositeInstance } from "@intentius/chant";
|
|
3
3
|
import { emitYAML } from "@intentius/chant/yaml";
|
|
4
4
|
import { WebApp } from "./web-app";
|
|
@@ -899,7 +899,7 @@ describe("NamespaceEnv", () => {
|
|
|
899
899
|
});
|
|
900
900
|
|
|
901
901
|
test("warns when quota set without limits", () => {
|
|
902
|
-
const warnSpy =
|
|
902
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
903
903
|
NamespaceEnv({ name: "warn-test", cpuQuota: "4", defaultDenyIngress: false });
|
|
904
904
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
905
905
|
expect.stringContaining("ResourceQuota set but no LimitRange defaults"),
|
|
@@ -908,7 +908,7 @@ describe("NamespaceEnv", () => {
|
|
|
908
908
|
});
|
|
909
909
|
|
|
910
910
|
test("no warning when both quota and limits set", () => {
|
|
911
|
-
const warnSpy =
|
|
911
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
912
912
|
NamespaceEnv({
|
|
913
913
|
name: "both-test",
|
|
914
914
|
cpuQuota: "4",
|
|
@@ -920,7 +920,7 @@ describe("NamespaceEnv", () => {
|
|
|
920
920
|
});
|
|
921
921
|
|
|
922
922
|
test("no warning when limits only (no quota)", () => {
|
|
923
|
-
const warnSpy =
|
|
923
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
924
924
|
NamespaceEnv({
|
|
925
925
|
name: "limits-only",
|
|
926
926
|
defaultCpuRequest: "100m",
|
package/src/composites/index.ts
CHANGED
|
@@ -73,3 +73,11 @@ export { AksExternalDnsAgent } from "./aks-external-dns-agent";
|
|
|
73
73
|
export type { AksExternalDnsAgentProps, AksExternalDnsAgentResult } from "./aks-external-dns-agent";
|
|
74
74
|
export { CockroachDbCluster } from "./cockroachdb-cluster";
|
|
75
75
|
export type { CockroachDbClusterProps, CockroachDbClusterResult } from "./cockroachdb-cluster";
|
|
76
|
+
export { CockroachDbRegionStack } from "./cockroachdb-region-stack";
|
|
77
|
+
export type { CockroachDbRegionStackConfig, CockroachDbRegionCockroachConfig, CockroachDbRegionTlsConfig } from "./cockroachdb-region-stack";
|
|
78
|
+
export { RayCluster } from "./ray-cluster";
|
|
79
|
+
export type { RayClusterProps, RayClusterResult, RayClusterSpec, ResourceSpec, HeadGroupSpec, WorkerGroupSpec } from "./ray-cluster";
|
|
80
|
+
export { RayJob } from "./ray-job";
|
|
81
|
+
export type { RayJobProps, RayJobResult } from "./ray-job";
|
|
82
|
+
export { RayService } from "./ray-service";
|
|
83
|
+
export type { RayServiceProps, RayServiceResult } from "./ray-service";
|