@intentius/chant-lexicon-k8s 0.0.22 → 0.1.0
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 +9 -4
- package/dist/manifest.json +1 -1
- package/dist/skills/chant-k8s-aks.md +146 -0
- package/{src/skills/kubernetes-patterns.md → dist/skills/chant-k8s-deployment-strategies.md} +1 -1
- package/dist/skills/chant-k8s-eks.md +156 -0
- package/dist/skills/chant-k8s-gke.md +246 -0
- package/{src/skills/kubernetes-security.md → dist/skills/chant-k8s-security.md} +1 -1
- package/dist/skills/chant-k8s.md +65 -2
- package/package.json +5 -4
- package/src/composites/adot-collector.ts +34 -22
- package/src/composites/agic-ingress.ts +14 -6
- package/src/composites/aks-external-dns-agent.ts +29 -18
- package/src/composites/alb-ingress.ts +14 -6
- package/src/composites/autoscaled-service.ts +25 -20
- package/src/composites/azure-disk-storage-class.ts +14 -6
- package/src/composites/azure-file-storage-class.ts +14 -6
- package/src/composites/azure-monitor-collector.ts +34 -22
- package/src/composites/batch-job.ts +25 -17
- package/src/composites/cockroachdb-cluster.ts +164 -58
- package/src/composites/composites.test.ts +371 -365
- package/src/composites/config-connector-context.ts +18 -11
- package/src/composites/configured-app.ts +21 -15
- package/src/composites/cron-workload.ts +25 -20
- package/src/composites/ebs-storage-class.ts +14 -6
- package/src/composites/efs-storage-class.ts +14 -6
- package/src/composites/external-dns-agent.ts +26 -20
- package/src/composites/filestore-storage-class.ts +14 -6
- package/src/composites/fluent-bit-agent.ts +30 -24
- package/src/composites/gce-ingress.ts +14 -6
- package/src/composites/gce-pd-storage-class.ts +14 -6
- package/src/composites/gke-external-dns-agent.ts +34 -21
- package/src/composites/gke-fluent-bit-agent.ts +34 -22
- package/src/composites/gke-gateway.ts +19 -12
- package/src/composites/gke-otel-collector.ts +34 -22
- package/src/composites/irsa-service-account.ts +22 -14
- package/src/composites/metrics-server.ts +41 -26
- package/src/composites/monitored-service.ts +26 -19
- package/src/composites/namespace-env.ts +26 -17
- package/src/composites/network-isolated-app.ts +21 -16
- package/src/composites/node-agent.ts +33 -22
- package/src/composites/secure-ingress.ts +19 -11
- package/src/composites/sidecar-app.ts +17 -12
- package/src/composites/stateful-app.ts +21 -12
- package/src/composites/web-app.ts +25 -21
- package/src/composites/worker-pool.ts +40 -26
- package/src/composites/workload-identity-sa.ts +22 -14
- package/src/composites/workload-identity-service-account.ts +22 -16
- package/src/plugin.ts +40 -614
- package/src/serializer.ts +7 -0
- package/src/skills/chant-k8s-deployment-strategies.md +183 -0
- package/src/skills/chant-k8s-gke.md +56 -1
- package/src/skills/chant-k8s-patterns.md +245 -0
- package/src/skills/chant-k8s-security.md +237 -0
- package/src/skills/chant-k8s.md +305 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { describe, test, expect, jest } from "bun:test";
|
|
2
|
+
import { isCompositeInstance } from "@intentius/chant";
|
|
2
3
|
import { emitYAML } from "@intentius/chant/yaml";
|
|
3
4
|
import { WebApp } from "./web-app";
|
|
5
|
+
|
|
6
|
+
/** Helper to access props on a Declarable member. */
|
|
7
|
+
function p(member: unknown): Record<string, unknown> {
|
|
8
|
+
return (member as any).props;
|
|
9
|
+
}
|
|
4
10
|
import { StatefulApp } from "./stateful-app";
|
|
5
11
|
import { CronWorkload } from "./cron-workload";
|
|
6
12
|
import { AutoscaledService } from "./autoscaled-service";
|
|
@@ -49,7 +55,7 @@ describe("WebApp", () => {
|
|
|
49
55
|
ingressHost: "app.example.com",
|
|
50
56
|
});
|
|
51
57
|
expect(result.ingress).toBeDefined();
|
|
52
|
-
const spec = result.ingress
|
|
58
|
+
const spec = p(result.ingress).spec as any;
|
|
53
59
|
expect(spec.rules[0].host).toBe("app.example.com");
|
|
54
60
|
});
|
|
55
61
|
|
|
@@ -60,7 +66,7 @@ describe("WebApp", () => {
|
|
|
60
66
|
ingressHost: "app.example.com",
|
|
61
67
|
ingressTlsSecret: "tls-secret",
|
|
62
68
|
});
|
|
63
|
-
const spec = result.ingress
|
|
69
|
+
const spec = p(result.ingress).spec as any;
|
|
64
70
|
expect(spec.tls).toBeDefined();
|
|
65
71
|
expect(spec.tls[0].secretName).toBe("tls-secret");
|
|
66
72
|
});
|
|
@@ -72,7 +78,7 @@ describe("WebApp", () => {
|
|
|
72
78
|
port: 3000,
|
|
73
79
|
replicas: 5,
|
|
74
80
|
});
|
|
75
|
-
const spec = result.deployment.spec as any;
|
|
81
|
+
const spec = p(result.deployment).spec as any;
|
|
76
82
|
expect(spec.replicas).toBe(5);
|
|
77
83
|
const container = spec.template.spec.containers[0];
|
|
78
84
|
expect(container.image).toBe("web:2.0");
|
|
@@ -81,26 +87,26 @@ describe("WebApp", () => {
|
|
|
81
87
|
|
|
82
88
|
test("default port is 80", () => {
|
|
83
89
|
const result = WebApp({ name: "app", image: "app:1.0" });
|
|
84
|
-
const spec = result.deployment.spec as any;
|
|
90
|
+
const spec = p(result.deployment).spec as any;
|
|
85
91
|
const container = spec.template.spec.containers[0];
|
|
86
92
|
expect(container.ports[0].containerPort).toBe(80);
|
|
87
93
|
});
|
|
88
94
|
|
|
89
95
|
test("default replicas is 2", () => {
|
|
90
96
|
const result = WebApp({ name: "app", image: "app:1.0" });
|
|
91
|
-
const spec = result.deployment.spec as any;
|
|
97
|
+
const spec = p(result.deployment).spec as any;
|
|
92
98
|
expect(spec.replicas).toBe(2);
|
|
93
99
|
});
|
|
94
100
|
|
|
95
101
|
test("service type is ClusterIP", () => {
|
|
96
102
|
const result = WebApp({ name: "app", image: "app:1.0" });
|
|
97
|
-
const spec = result.service.spec as any;
|
|
103
|
+
const spec = p(result.service).spec as any;
|
|
98
104
|
expect(spec.type).toBe("ClusterIP");
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
test("includes common labels", () => {
|
|
102
108
|
const result = WebApp({ name: "app", image: "app:1.0" });
|
|
103
|
-
const meta = result.deployment.metadata as any;
|
|
109
|
+
const meta = p(result.deployment).metadata as any;
|
|
104
110
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("app");
|
|
105
111
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
106
112
|
});
|
|
@@ -111,7 +117,7 @@ describe("WebApp", () => {
|
|
|
111
117
|
image: "app:1.0",
|
|
112
118
|
namespace: "prod",
|
|
113
119
|
});
|
|
114
|
-
const meta = result.deployment.metadata as any;
|
|
120
|
+
const meta = p(result.deployment).metadata as any;
|
|
115
121
|
expect(meta.namespace).toBe("prod");
|
|
116
122
|
});
|
|
117
123
|
|
|
@@ -121,7 +127,7 @@ describe("WebApp", () => {
|
|
|
121
127
|
image: "app:1.0",
|
|
122
128
|
env: [{ name: "FOO", value: "bar" }],
|
|
123
129
|
});
|
|
124
|
-
const spec = result.deployment.spec as any;
|
|
130
|
+
const spec = p(result.deployment).spec as any;
|
|
125
131
|
const container = spec.template.spec.containers[0];
|
|
126
132
|
expect(container.env).toEqual([{ name: "FOO", value: "bar" }]);
|
|
127
133
|
});
|
|
@@ -132,9 +138,9 @@ describe("WebApp", () => {
|
|
|
132
138
|
image: "app:1.0",
|
|
133
139
|
ingressHost: "app.example.com",
|
|
134
140
|
});
|
|
135
|
-
expect((result.deployment.metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
136
|
-
expect((result.service.metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
137
|
-
expect((result.ingress
|
|
141
|
+
expect((p(result.deployment).metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
142
|
+
expect((p(result.service).metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
143
|
+
expect((p(result.ingress).metadata as any).labels["app.kubernetes.io/component"]).toBe("ingress");
|
|
138
144
|
});
|
|
139
145
|
});
|
|
140
146
|
|
|
@@ -149,7 +155,7 @@ describe("StatefulApp", () => {
|
|
|
149
155
|
|
|
150
156
|
test("service is headless (clusterIP: None)", () => {
|
|
151
157
|
const result = StatefulApp({ name: "db", image: "postgres:16" });
|
|
152
|
-
const spec = result.service.spec as any;
|
|
158
|
+
const spec = p(result.service).spec as any;
|
|
153
159
|
expect(spec.clusterIP).toBe("None");
|
|
154
160
|
});
|
|
155
161
|
|
|
@@ -159,7 +165,7 @@ describe("StatefulApp", () => {
|
|
|
159
165
|
image: "postgres:16",
|
|
160
166
|
storageSize: "20Gi",
|
|
161
167
|
});
|
|
162
|
-
const spec = result.statefulSet.spec as any;
|
|
168
|
+
const spec = p(result.statefulSet).spec as any;
|
|
163
169
|
expect(spec.volumeClaimTemplates).toBeDefined();
|
|
164
170
|
expect(spec.volumeClaimTemplates[0].spec.resources.requests.storage).toBe(
|
|
165
171
|
"20Gi",
|
|
@@ -168,20 +174,20 @@ describe("StatefulApp", () => {
|
|
|
168
174
|
|
|
169
175
|
test("default port is 5432", () => {
|
|
170
176
|
const result = StatefulApp({ name: "db", image: "postgres:16" });
|
|
171
|
-
const spec = result.statefulSet.spec as any;
|
|
177
|
+
const spec = p(result.statefulSet).spec as any;
|
|
172
178
|
const container = spec.template.spec.containers[0];
|
|
173
179
|
expect(container.ports[0].containerPort).toBe(5432);
|
|
174
180
|
});
|
|
175
181
|
|
|
176
182
|
test("default replicas is 1", () => {
|
|
177
183
|
const result = StatefulApp({ name: "db", image: "postgres:16" });
|
|
178
|
-
const spec = result.statefulSet.spec as any;
|
|
184
|
+
const spec = p(result.statefulSet).spec as any;
|
|
179
185
|
expect(spec.replicas).toBe(1);
|
|
180
186
|
});
|
|
181
187
|
|
|
182
188
|
test("serviceName matches name", () => {
|
|
183
189
|
const result = StatefulApp({ name: "db", image: "postgres:16" });
|
|
184
|
-
const spec = result.statefulSet.spec as any;
|
|
190
|
+
const spec = p(result.statefulSet).spec as any;
|
|
185
191
|
expect(spec.serviceName).toBe("db");
|
|
186
192
|
});
|
|
187
193
|
|
|
@@ -191,14 +197,14 @@ describe("StatefulApp", () => {
|
|
|
191
197
|
image: "postgres:16",
|
|
192
198
|
storageClassName: "ssd",
|
|
193
199
|
});
|
|
194
|
-
const spec = result.statefulSet.spec as any;
|
|
200
|
+
const spec = p(result.statefulSet).spec as any;
|
|
195
201
|
expect(spec.volumeClaimTemplates[0].spec.storageClassName).toBe("ssd");
|
|
196
202
|
});
|
|
197
203
|
|
|
198
204
|
test("component labels on each resource", () => {
|
|
199
205
|
const result = StatefulApp({ name: "db", image: "postgres:16" });
|
|
200
|
-
expect((result.statefulSet.metadata as any).labels["app.kubernetes.io/component"]).toBe("database");
|
|
201
|
-
expect((result.service.metadata as any).labels["app.kubernetes.io/component"]).toBe("database");
|
|
206
|
+
expect((p(result.statefulSet).metadata as any).labels["app.kubernetes.io/component"]).toBe("database");
|
|
207
|
+
expect((p(result.service).metadata as any).labels["app.kubernetes.io/component"]).toBe("database");
|
|
202
208
|
});
|
|
203
209
|
});
|
|
204
210
|
|
|
@@ -223,7 +229,7 @@ describe("CronWorkload", () => {
|
|
|
223
229
|
image: "backup:1.0",
|
|
224
230
|
schedule: "0 2 * * *",
|
|
225
231
|
});
|
|
226
|
-
const spec = result.cronJob.spec as any;
|
|
232
|
+
const spec = p(result.cronJob).spec as any;
|
|
227
233
|
expect(spec.schedule).toBe("0 2 * * *");
|
|
228
234
|
});
|
|
229
235
|
|
|
@@ -233,7 +239,7 @@ describe("CronWorkload", () => {
|
|
|
233
239
|
image: "backup:1.0",
|
|
234
240
|
schedule: "0 2 * * *",
|
|
235
241
|
});
|
|
236
|
-
const binding = result.roleBinding as any;
|
|
242
|
+
const binding = p(result.roleBinding) as any;
|
|
237
243
|
expect(binding.subjects[0].name).toBe("backup-sa");
|
|
238
244
|
expect(binding.roleRef.name).toBe("backup-role");
|
|
239
245
|
});
|
|
@@ -244,7 +250,7 @@ describe("CronWorkload", () => {
|
|
|
244
250
|
image: "cleanup:1.0",
|
|
245
251
|
schedule: "*/5 * * * *",
|
|
246
252
|
});
|
|
247
|
-
const meta = result.serviceAccount.metadata as any;
|
|
253
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
248
254
|
expect(meta.name).toBe("cleanup-sa");
|
|
249
255
|
});
|
|
250
256
|
|
|
@@ -257,7 +263,7 @@ describe("CronWorkload", () => {
|
|
|
257
263
|
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
258
264
|
],
|
|
259
265
|
});
|
|
260
|
-
const role = result.role as any;
|
|
266
|
+
const role = p(result.role) as any;
|
|
261
267
|
expect(role.rules[0].resources).toEqual(["secrets"]);
|
|
262
268
|
expect(role.rules[0].verbs).toEqual(["get"]);
|
|
263
269
|
});
|
|
@@ -268,7 +274,7 @@ describe("CronWorkload", () => {
|
|
|
268
274
|
image: "backup:1.0",
|
|
269
275
|
schedule: "0 2 * * *",
|
|
270
276
|
});
|
|
271
|
-
const role = result.role as any;
|
|
277
|
+
const role = p(result.role) as any;
|
|
272
278
|
expect(role.rules.length).toBeGreaterThan(0);
|
|
273
279
|
});
|
|
274
280
|
|
|
@@ -280,7 +286,7 @@ describe("CronWorkload", () => {
|
|
|
280
286
|
command: ["pg_dump"],
|
|
281
287
|
args: ["-h", "postgres"],
|
|
282
288
|
});
|
|
283
|
-
const spec = result.cronJob.spec as any;
|
|
289
|
+
const spec = p(result.cronJob).spec as any;
|
|
284
290
|
const container =
|
|
285
291
|
spec.jobTemplate.spec.template.spec.containers[0];
|
|
286
292
|
expect(container.command).toEqual(["pg_dump"]);
|
|
@@ -293,7 +299,7 @@ describe("CronWorkload", () => {
|
|
|
293
299
|
image: "backup:1.0",
|
|
294
300
|
schedule: "0 2 * * *",
|
|
295
301
|
});
|
|
296
|
-
const spec = result.cronJob.spec as any;
|
|
302
|
+
const spec = p(result.cronJob).spec as any;
|
|
297
303
|
expect(spec.jobTemplate.spec.template.spec.restartPolicy).toBe(
|
|
298
304
|
"OnFailure",
|
|
299
305
|
);
|
|
@@ -312,7 +318,7 @@ describe("CronWorkload", () => {
|
|
|
312
318
|
result.role,
|
|
313
319
|
result.roleBinding,
|
|
314
320
|
]) {
|
|
315
|
-
const meta = resource.metadata as any;
|
|
321
|
+
const meta = p(resource).metadata as any;
|
|
316
322
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("backup");
|
|
317
323
|
}
|
|
318
324
|
});
|
|
@@ -323,10 +329,10 @@ describe("CronWorkload", () => {
|
|
|
323
329
|
image: "backup:1.0",
|
|
324
330
|
schedule: "0 2 * * *",
|
|
325
331
|
});
|
|
326
|
-
expect((result.cronJob.metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
327
|
-
expect((result.serviceAccount.metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
328
|
-
expect((result.role.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
329
|
-
expect((result.roleBinding.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
332
|
+
expect((p(result.cronJob).metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
333
|
+
expect((p(result.serviceAccount).metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
334
|
+
expect((p(result.role).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
335
|
+
expect((p(result.roleBinding).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
330
336
|
});
|
|
331
337
|
});
|
|
332
338
|
|
|
@@ -351,36 +357,36 @@ describe("AutoscaledService", () => {
|
|
|
351
357
|
|
|
352
358
|
test("default port is 80", () => {
|
|
353
359
|
const result = AutoscaledService(minProps);
|
|
354
|
-
const spec = result.deployment.spec as any;
|
|
360
|
+
const spec = p(result.deployment).spec as any;
|
|
355
361
|
const container = spec.template.spec.containers[0];
|
|
356
362
|
expect(container.ports[0].containerPort).toBe(80);
|
|
357
363
|
});
|
|
358
364
|
|
|
359
365
|
test("default minReplicas is 2", () => {
|
|
360
366
|
const result = AutoscaledService(minProps);
|
|
361
|
-
const spec = result.deployment.spec as any;
|
|
367
|
+
const spec = p(result.deployment).spec as any;
|
|
362
368
|
expect(spec.replicas).toBe(2);
|
|
363
|
-
const hpaSpec = result.hpa.spec as any;
|
|
369
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
364
370
|
expect(hpaSpec.minReplicas).toBe(2);
|
|
365
371
|
});
|
|
366
372
|
|
|
367
373
|
test("HPA scaleTargetRef references the deployment", () => {
|
|
368
374
|
const result = AutoscaledService(minProps);
|
|
369
|
-
const hpaSpec = result.hpa.spec as any;
|
|
375
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
370
376
|
expect(hpaSpec.scaleTargetRef.kind).toBe("Deployment");
|
|
371
377
|
expect(hpaSpec.scaleTargetRef.name).toBe("api");
|
|
372
378
|
});
|
|
373
379
|
|
|
374
380
|
test("HPA has CPU metric with default 70%", () => {
|
|
375
381
|
const result = AutoscaledService(minProps);
|
|
376
|
-
const hpaSpec = result.hpa.spec as any;
|
|
382
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
377
383
|
expect(hpaSpec.metrics[0].resource.name).toBe("cpu");
|
|
378
384
|
expect(hpaSpec.metrics[0].resource.target.averageUtilization).toBe(70);
|
|
379
385
|
});
|
|
380
386
|
|
|
381
387
|
test("HPA includes memory metric when targetMemoryPercent set", () => {
|
|
382
388
|
const result = AutoscaledService({ ...minProps, targetMemoryPercent: 80 });
|
|
383
|
-
const hpaSpec = result.hpa.spec as any;
|
|
389
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
384
390
|
expect(hpaSpec.metrics).toHaveLength(2);
|
|
385
391
|
expect(hpaSpec.metrics[1].resource.name).toBe("memory");
|
|
386
392
|
expect(hpaSpec.metrics[1].resource.target.averageUtilization).toBe(80);
|
|
@@ -388,20 +394,20 @@ describe("AutoscaledService", () => {
|
|
|
388
394
|
|
|
389
395
|
test("no memory metric by default", () => {
|
|
390
396
|
const result = AutoscaledService(minProps);
|
|
391
|
-
const hpaSpec = result.hpa.spec as any;
|
|
397
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
392
398
|
expect(hpaSpec.metrics).toHaveLength(1);
|
|
393
399
|
});
|
|
394
400
|
|
|
395
401
|
test("PDB selector matches deployment pod labels", () => {
|
|
396
402
|
const result = AutoscaledService(minProps);
|
|
397
|
-
const pdbSpec = result.pdb.spec as any;
|
|
403
|
+
const pdbSpec = p(result.pdb).spec as any;
|
|
398
404
|
expect(pdbSpec.selector.matchLabels["app.kubernetes.io/name"]).toBe("api");
|
|
399
405
|
expect(pdbSpec.minAvailable).toBe(1);
|
|
400
406
|
});
|
|
401
407
|
|
|
402
408
|
test("resource requests are always set", () => {
|
|
403
409
|
const result = AutoscaledService(minProps);
|
|
404
|
-
const spec = result.deployment.spec as any;
|
|
410
|
+
const spec = p(result.deployment).spec as any;
|
|
405
411
|
const container = spec.template.spec.containers[0];
|
|
406
412
|
expect(container.resources.requests.cpu).toBe("100m");
|
|
407
413
|
expect(container.resources.requests.memory).toBe("128Mi");
|
|
@@ -409,12 +415,12 @@ describe("AutoscaledService", () => {
|
|
|
409
415
|
|
|
410
416
|
test("resource limits only set when provided", () => {
|
|
411
417
|
const result = AutoscaledService(minProps);
|
|
412
|
-
const spec = result.deployment.spec as any;
|
|
418
|
+
const spec = p(result.deployment).spec as any;
|
|
413
419
|
const container = spec.template.spec.containers[0];
|
|
414
420
|
expect(container.resources.limits).toBeUndefined();
|
|
415
421
|
|
|
416
422
|
const withLimits = AutoscaledService({ ...minProps, cpuLimit: "1", memoryLimit: "512Mi" });
|
|
417
|
-
const spec2 = withLimits.deployment.spec as any;
|
|
423
|
+
const spec2 = p(withLimits.deployment).spec as any;
|
|
418
424
|
const container2 = spec2.template.spec.containers[0];
|
|
419
425
|
expect(container2.resources.limits.cpu).toBe("1");
|
|
420
426
|
expect(container2.resources.limits.memory).toBe("512Mi");
|
|
@@ -422,7 +428,7 @@ describe("AutoscaledService", () => {
|
|
|
422
428
|
|
|
423
429
|
test("includes health probes", () => {
|
|
424
430
|
const result = AutoscaledService(minProps);
|
|
425
|
-
const spec = result.deployment.spec as any;
|
|
431
|
+
const spec = p(result.deployment).spec as any;
|
|
426
432
|
const container = spec.template.spec.containers[0];
|
|
427
433
|
expect(container.livenessProbe).toBeDefined();
|
|
428
434
|
expect(container.readinessProbe).toBeDefined();
|
|
@@ -430,14 +436,14 @@ describe("AutoscaledService", () => {
|
|
|
430
436
|
|
|
431
437
|
test("service type is ClusterIP", () => {
|
|
432
438
|
const result = AutoscaledService(minProps);
|
|
433
|
-
const spec = result.service.spec as any;
|
|
439
|
+
const spec = p(result.service).spec as any;
|
|
434
440
|
expect(spec.type).toBe("ClusterIP");
|
|
435
441
|
});
|
|
436
442
|
|
|
437
443
|
test("includes common labels on all resources", () => {
|
|
438
444
|
const result = AutoscaledService(minProps);
|
|
439
445
|
for (const resource of [result.deployment, result.service, result.hpa, result.pdb]) {
|
|
440
|
-
const meta = resource.metadata as any;
|
|
446
|
+
const meta = p(resource).metadata as any;
|
|
441
447
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("api");
|
|
442
448
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
443
449
|
}
|
|
@@ -446,7 +452,7 @@ describe("AutoscaledService", () => {
|
|
|
446
452
|
test("namespace propagated to all resources", () => {
|
|
447
453
|
const result = AutoscaledService({ ...minProps, namespace: "prod" });
|
|
448
454
|
for (const resource of [result.deployment, result.service, result.hpa, result.pdb]) {
|
|
449
|
-
const meta = resource.metadata as any;
|
|
455
|
+
const meta = p(resource).metadata as any;
|
|
450
456
|
expect(meta.namespace).toBe("prod");
|
|
451
457
|
}
|
|
452
458
|
});
|
|
@@ -456,22 +462,22 @@ describe("AutoscaledService", () => {
|
|
|
456
462
|
...minProps,
|
|
457
463
|
env: [{ name: "LOG_LEVEL", value: "debug" }],
|
|
458
464
|
});
|
|
459
|
-
const spec = result.deployment.spec as any;
|
|
465
|
+
const spec = p(result.deployment).spec as any;
|
|
460
466
|
const container = spec.template.spec.containers[0];
|
|
461
467
|
expect(container.env).toEqual([{ name: "LOG_LEVEL", value: "debug" }]);
|
|
462
468
|
});
|
|
463
469
|
|
|
464
470
|
test("component labels on each resource", () => {
|
|
465
471
|
const result = AutoscaledService(minProps);
|
|
466
|
-
expect((result.deployment.metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
467
|
-
expect((result.service.metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
468
|
-
expect((result.hpa.metadata as any).labels["app.kubernetes.io/component"]).toBe("autoscaler");
|
|
469
|
-
expect((result.pdb.metadata as any).labels["app.kubernetes.io/component"]).toBe("disruption-budget");
|
|
472
|
+
expect((p(result.deployment).metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
473
|
+
expect((p(result.service).metadata as any).labels["app.kubernetes.io/component"]).toBe("server");
|
|
474
|
+
expect((p(result.hpa).metadata as any).labels["app.kubernetes.io/component"]).toBe("autoscaler");
|
|
475
|
+
expect((p(result.pdb).metadata as any).labels["app.kubernetes.io/component"]).toBe("disruption-budget");
|
|
470
476
|
});
|
|
471
477
|
|
|
472
478
|
test("default probe paths are /healthz and /readyz", () => {
|
|
473
479
|
const result = AutoscaledService(minProps);
|
|
474
|
-
const spec = result.deployment.spec as any;
|
|
480
|
+
const spec = p(result.deployment).spec as any;
|
|
475
481
|
const container = spec.template.spec.containers[0];
|
|
476
482
|
expect(container.livenessProbe.httpGet.path).toBe("/healthz");
|
|
477
483
|
expect(container.readinessProbe.httpGet.path).toBe("/readyz");
|
|
@@ -483,7 +489,7 @@ describe("AutoscaledService", () => {
|
|
|
483
489
|
livenessPath: "/alive",
|
|
484
490
|
readinessPath: "/ready",
|
|
485
491
|
});
|
|
486
|
-
const spec = result.deployment.spec as any;
|
|
492
|
+
const spec = p(result.deployment).spec as any;
|
|
487
493
|
const container = spec.template.spec.containers[0];
|
|
488
494
|
expect(container.livenessProbe.httpGet.path).toBe("/alive");
|
|
489
495
|
expect(container.readinessProbe.httpGet.path).toBe("/ready");
|
|
@@ -491,7 +497,7 @@ describe("AutoscaledService", () => {
|
|
|
491
497
|
|
|
492
498
|
test("probe targets container port", () => {
|
|
493
499
|
const result = AutoscaledService({ ...minProps, port: 8080 });
|
|
494
|
-
const spec = result.deployment.spec as any;
|
|
500
|
+
const spec = p(result.deployment).spec as any;
|
|
495
501
|
const container = spec.template.spec.containers[0];
|
|
496
502
|
expect(container.livenessProbe.httpGet.port).toBe(8080);
|
|
497
503
|
expect(container.readinessProbe.httpGet.port).toBe(8080);
|
|
@@ -499,13 +505,13 @@ describe("AutoscaledService", () => {
|
|
|
499
505
|
|
|
500
506
|
test("topologySpread not present by default", () => {
|
|
501
507
|
const result = AutoscaledService(minProps);
|
|
502
|
-
const spec = result.deployment.spec as any;
|
|
508
|
+
const spec = p(result.deployment).spec as any;
|
|
503
509
|
expect(spec.template.spec.topologySpreadConstraints).toBeUndefined();
|
|
504
510
|
});
|
|
505
511
|
|
|
506
512
|
test("topologySpread: true adds zone constraint", () => {
|
|
507
513
|
const result = AutoscaledService({ ...minProps, topologySpread: true });
|
|
508
|
-
const spec = result.deployment.spec as any;
|
|
514
|
+
const spec = p(result.deployment).spec as any;
|
|
509
515
|
const tsc = spec.template.spec.topologySpreadConstraints;
|
|
510
516
|
expect(tsc).toHaveLength(1);
|
|
511
517
|
expect(tsc[0].topologyKey).toBe("topology.kubernetes.io/zone");
|
|
@@ -519,7 +525,7 @@ describe("AutoscaledService", () => {
|
|
|
519
525
|
...minProps,
|
|
520
526
|
topologySpread: { maxSkew: 2, topologyKey: "kubernetes.io/hostname" },
|
|
521
527
|
});
|
|
522
|
-
const spec = result.deployment.spec as any;
|
|
528
|
+
const spec = p(result.deployment).spec as any;
|
|
523
529
|
const tsc = spec.template.spec.topologySpreadConstraints;
|
|
524
530
|
expect(tsc[0].maxSkew).toBe(2);
|
|
525
531
|
expect(tsc[0].topologyKey).toBe("kubernetes.io/hostname");
|
|
@@ -527,19 +533,19 @@ describe("AutoscaledService", () => {
|
|
|
527
533
|
|
|
528
534
|
test("minAvailable as string percentage", () => {
|
|
529
535
|
const result = AutoscaledService({ ...minProps, minAvailable: "50%" });
|
|
530
|
-
const pdbSpec = result.pdb.spec as any;
|
|
536
|
+
const pdbSpec = p(result.pdb).spec as any;
|
|
531
537
|
expect(pdbSpec.minAvailable).toBe("50%");
|
|
532
538
|
});
|
|
533
539
|
|
|
534
540
|
test("custom targetCPUPercent", () => {
|
|
535
541
|
const result = AutoscaledService({ ...minProps, targetCPUPercent: 85 });
|
|
536
|
-
const hpaSpec = result.hpa.spec as any;
|
|
542
|
+
const hpaSpec = p(result.hpa).spec as any;
|
|
537
543
|
expect(hpaSpec.metrics[0].resource.target.averageUtilization).toBe(85);
|
|
538
544
|
});
|
|
539
545
|
|
|
540
546
|
test("pod template labels include extra labels", () => {
|
|
541
547
|
const result = AutoscaledService({ ...minProps, labels: { team: "platform" } });
|
|
542
|
-
const spec = result.deployment.spec as any;
|
|
548
|
+
const spec = p(result.deployment).spec as any;
|
|
543
549
|
const podLabels = spec.template.metadata.labels;
|
|
544
550
|
expect(podLabels.team).toBe("platform");
|
|
545
551
|
expect(podLabels["app.kubernetes.io/name"]).toBe("api");
|
|
@@ -547,13 +553,13 @@ describe("AutoscaledService", () => {
|
|
|
547
553
|
|
|
548
554
|
test("serviceAccountName wired into pod spec", () => {
|
|
549
555
|
const result = AutoscaledService({ ...minProps, serviceAccountName: "my-sa" });
|
|
550
|
-
const spec = result.deployment.spec as any;
|
|
556
|
+
const spec = p(result.deployment).spec as any;
|
|
551
557
|
expect(spec.template.spec.serviceAccountName).toBe("my-sa");
|
|
552
558
|
});
|
|
553
559
|
|
|
554
560
|
test("no serviceAccountName by default", () => {
|
|
555
561
|
const result = AutoscaledService(minProps);
|
|
556
|
-
const spec = result.deployment.spec as any;
|
|
562
|
+
const spec = p(result.deployment).spec as any;
|
|
557
563
|
expect(spec.template.spec.serviceAccountName).toBeUndefined();
|
|
558
564
|
});
|
|
559
565
|
|
|
@@ -563,7 +569,7 @@ describe("AutoscaledService", () => {
|
|
|
563
569
|
volumes: [{ name: "data", emptyDir: {} }],
|
|
564
570
|
volumeMounts: [{ name: "data", mountPath: "/data" }],
|
|
565
571
|
});
|
|
566
|
-
const spec = result.deployment.spec as any;
|
|
572
|
+
const spec = p(result.deployment).spec as any;
|
|
567
573
|
expect(spec.template.spec.volumes).toEqual([{ name: "data", emptyDir: {} }]);
|
|
568
574
|
expect(spec.template.spec.containers[0].volumeMounts).toEqual([{ name: "data", mountPath: "/data" }]);
|
|
569
575
|
});
|
|
@@ -573,7 +579,7 @@ describe("AutoscaledService", () => {
|
|
|
573
579
|
...minProps,
|
|
574
580
|
tmpDirs: ["/tmp", "/var/cache/nginx"],
|
|
575
581
|
});
|
|
576
|
-
const spec = result.deployment.spec as any;
|
|
582
|
+
const spec = p(result.deployment).spec as any;
|
|
577
583
|
expect(spec.template.spec.volumes).toEqual([
|
|
578
584
|
{ name: "tmp-0", emptyDir: {} },
|
|
579
585
|
{ name: "tmp-1", emptyDir: {} },
|
|
@@ -591,7 +597,7 @@ describe("AutoscaledService", () => {
|
|
|
591
597
|
volumeMounts: [{ name: "config", mountPath: "/etc/config" }],
|
|
592
598
|
tmpDirs: ["/tmp"],
|
|
593
599
|
});
|
|
594
|
-
const spec = result.deployment.spec as any;
|
|
600
|
+
const spec = p(result.deployment).spec as any;
|
|
595
601
|
expect(spec.template.spec.volumes).toEqual([
|
|
596
602
|
{ name: "config", configMap: { name: "app-config" } },
|
|
597
603
|
{ name: "tmp-0", emptyDir: {} },
|
|
@@ -624,15 +630,15 @@ describe("WorkerPool", () => {
|
|
|
624
630
|
|
|
625
631
|
test("default replicas is 1", () => {
|
|
626
632
|
const result = WorkerPool(minProps);
|
|
627
|
-
const spec = result.deployment.spec as any;
|
|
633
|
+
const spec = p(result.deployment).spec as any;
|
|
628
634
|
expect(spec.replicas).toBe(1);
|
|
629
635
|
});
|
|
630
636
|
|
|
631
637
|
test("RBAC naming convention", () => {
|
|
632
638
|
const result = WorkerPool(minProps);
|
|
633
|
-
const saMeta = result.serviceAccount
|
|
634
|
-
const roleMeta = result.role
|
|
635
|
-
const bindingMeta = result.roleBinding
|
|
639
|
+
const saMeta = p(result.serviceAccount).metadata as any;
|
|
640
|
+
const roleMeta = p(result.role).metadata as any;
|
|
641
|
+
const bindingMeta = p(result.roleBinding).metadata as any;
|
|
636
642
|
expect(saMeta.name).toBe("worker-sa");
|
|
637
643
|
expect(roleMeta.name).toBe("worker-role");
|
|
638
644
|
expect(bindingMeta.name).toBe("worker-binding");
|
|
@@ -640,7 +646,7 @@ describe("WorkerPool", () => {
|
|
|
640
646
|
|
|
641
647
|
test("default RBAC rules for secrets and configmaps", () => {
|
|
642
648
|
const result = WorkerPool(minProps);
|
|
643
|
-
const role = result.role! as any;
|
|
649
|
+
const role = p(result.role!) as any;
|
|
644
650
|
expect(role.rules[0].resources).toEqual(["secrets", "configmaps"]);
|
|
645
651
|
expect(role.rules[0].verbs).toEqual(["get"]);
|
|
646
652
|
});
|
|
@@ -650,7 +656,7 @@ describe("WorkerPool", () => {
|
|
|
650
656
|
...minProps,
|
|
651
657
|
rbacRules: [{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["create"] }],
|
|
652
658
|
});
|
|
653
|
-
const role = result.role! as any;
|
|
659
|
+
const role = p(result.role!) as any;
|
|
654
660
|
expect(role.rules[0].resources).toEqual(["jobs"]);
|
|
655
661
|
});
|
|
656
662
|
|
|
@@ -660,7 +666,7 @@ describe("WorkerPool", () => {
|
|
|
660
666
|
command: ["bundle", "exec", "sidekiq"],
|
|
661
667
|
args: ["-c", "5"],
|
|
662
668
|
});
|
|
663
|
-
const spec = result.deployment.spec as any;
|
|
669
|
+
const spec = p(result.deployment).spec as any;
|
|
664
670
|
const container = spec.template.spec.containers[0];
|
|
665
671
|
expect(container.command).toEqual(["bundle", "exec", "sidekiq"]);
|
|
666
672
|
expect(container.args).toEqual(["-c", "5"]);
|
|
@@ -672,10 +678,10 @@ describe("WorkerPool", () => {
|
|
|
672
678
|
config: { REDIS_URL: "redis://redis:6379" },
|
|
673
679
|
});
|
|
674
680
|
expect(result.configMap).toBeDefined();
|
|
675
|
-
const data = (result.configMap as any).data;
|
|
681
|
+
const data = (p(result.configMap) as any).data;
|
|
676
682
|
expect(data.REDIS_URL).toBe("redis://redis:6379");
|
|
677
683
|
|
|
678
|
-
const spec = result.deployment.spec as any;
|
|
684
|
+
const spec = p(result.deployment).spec as any;
|
|
679
685
|
const container = spec.template.spec.containers[0];
|
|
680
686
|
expect(container.envFrom[0].configMapRef.name).toBe("worker-config");
|
|
681
687
|
});
|
|
@@ -686,18 +692,18 @@ describe("WorkerPool", () => {
|
|
|
686
692
|
autoscaling: { minReplicas: 2, maxReplicas: 8, targetCPUPercent: 60 },
|
|
687
693
|
});
|
|
688
694
|
expect(result.hpa).toBeDefined();
|
|
689
|
-
const hpaSpec = (result.hpa as any).spec;
|
|
695
|
+
const hpaSpec = (p(result.hpa) as any).spec;
|
|
690
696
|
expect(hpaSpec.minReplicas).toBe(2);
|
|
691
697
|
expect(hpaSpec.maxReplicas).toBe(8);
|
|
692
698
|
expect(hpaSpec.scaleTargetRef.name).toBe("worker");
|
|
693
699
|
|
|
694
|
-
const spec = result.deployment.spec as any;
|
|
700
|
+
const spec = p(result.deployment).spec as any;
|
|
695
701
|
expect(spec.replicas).toBe(2);
|
|
696
702
|
});
|
|
697
703
|
|
|
698
704
|
test("default resource limits", () => {
|
|
699
705
|
const result = WorkerPool(minProps);
|
|
700
|
-
const spec = result.deployment.spec as any;
|
|
706
|
+
const spec = p(result.deployment).spec as any;
|
|
701
707
|
const container = spec.template.spec.containers[0];
|
|
702
708
|
expect(container.resources.requests.cpu).toBe("100m");
|
|
703
709
|
expect(container.resources.requests.memory).toBe("128Mi");
|
|
@@ -707,14 +713,14 @@ describe("WorkerPool", () => {
|
|
|
707
713
|
|
|
708
714
|
test("serviceAccountName on pod spec", () => {
|
|
709
715
|
const result = WorkerPool(minProps);
|
|
710
|
-
const spec = result.deployment.spec as any;
|
|
716
|
+
const spec = p(result.deployment).spec as any;
|
|
711
717
|
expect(spec.template.spec.serviceAccountName).toBe("worker-sa");
|
|
712
718
|
});
|
|
713
719
|
|
|
714
720
|
test("includes common labels on all resources", () => {
|
|
715
721
|
const result = WorkerPool(minProps);
|
|
716
722
|
for (const resource of [result.deployment, result.serviceAccount!, result.role!, result.roleBinding!]) {
|
|
717
|
-
const meta = resource.metadata as any;
|
|
723
|
+
const meta = p(resource).metadata as any;
|
|
718
724
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("worker");
|
|
719
725
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
720
726
|
}
|
|
@@ -723,7 +729,7 @@ describe("WorkerPool", () => {
|
|
|
723
729
|
test("namespace propagated", () => {
|
|
724
730
|
const result = WorkerPool({ ...minProps, namespace: "jobs" });
|
|
725
731
|
for (const resource of [result.deployment, result.serviceAccount!, result.role!, result.roleBinding!]) {
|
|
726
|
-
const meta = resource.metadata as any;
|
|
732
|
+
const meta = p(resource).metadata as any;
|
|
727
733
|
expect(meta.namespace).toBe("jobs");
|
|
728
734
|
}
|
|
729
735
|
});
|
|
@@ -733,7 +739,7 @@ describe("WorkerPool", () => {
|
|
|
733
739
|
expect(result.serviceAccount).toBeUndefined();
|
|
734
740
|
expect(result.role).toBeUndefined();
|
|
735
741
|
expect(result.roleBinding).toBeUndefined();
|
|
736
|
-
const spec = result.deployment.spec as any;
|
|
742
|
+
const spec = p(result.deployment).spec as any;
|
|
737
743
|
expect(spec.template.spec.serviceAccountName).toBeUndefined();
|
|
738
744
|
});
|
|
739
745
|
|
|
@@ -742,7 +748,7 @@ describe("WorkerPool", () => {
|
|
|
742
748
|
expect(result.serviceAccount).toBeDefined();
|
|
743
749
|
expect(result.role).toBeDefined();
|
|
744
750
|
expect(result.roleBinding).toBeDefined();
|
|
745
|
-
const role = result.role as any;
|
|
751
|
+
const role = p(result.role) as any;
|
|
746
752
|
expect(role.rules[0].resources).toEqual(["secrets", "configmaps"]);
|
|
747
753
|
});
|
|
748
754
|
|
|
@@ -751,7 +757,7 @@ describe("WorkerPool", () => {
|
|
|
751
757
|
...minProps,
|
|
752
758
|
env: [{ name: "LOG_LEVEL", value: "debug" }],
|
|
753
759
|
});
|
|
754
|
-
const spec = result.deployment.spec as any;
|
|
760
|
+
const spec = p(result.deployment).spec as any;
|
|
755
761
|
const container = spec.template.spec.containers[0];
|
|
756
762
|
expect(container.env).toEqual([{ name: "LOG_LEVEL", value: "debug" }]);
|
|
757
763
|
});
|
|
@@ -762,7 +768,7 @@ describe("WorkerPool", () => {
|
|
|
762
768
|
config: { KEY: "val" },
|
|
763
769
|
namespace: "jobs",
|
|
764
770
|
});
|
|
765
|
-
const meta = (result.configMap as any).metadata;
|
|
771
|
+
const meta = (p(result.configMap) as any).metadata;
|
|
766
772
|
expect(meta.namespace).toBe("jobs");
|
|
767
773
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("worker");
|
|
768
774
|
});
|
|
@@ -773,7 +779,7 @@ describe("WorkerPool", () => {
|
|
|
773
779
|
autoscaling: { minReplicas: 1, maxReplicas: 5 },
|
|
774
780
|
namespace: "jobs",
|
|
775
781
|
});
|
|
776
|
-
const meta = (result.hpa as any).metadata;
|
|
782
|
+
const meta = (p(result.hpa) as any).metadata;
|
|
777
783
|
expect(meta.namespace).toBe("jobs");
|
|
778
784
|
});
|
|
779
785
|
|
|
@@ -782,7 +788,7 @@ describe("WorkerPool", () => {
|
|
|
782
788
|
...minProps,
|
|
783
789
|
autoscaling: { minReplicas: 1, maxReplicas: 5 },
|
|
784
790
|
});
|
|
785
|
-
const hpaSpec = (result.hpa as any).spec;
|
|
791
|
+
const hpaSpec = (p(result.hpa) as any).spec;
|
|
786
792
|
expect(hpaSpec.metrics[0].resource.target.averageUtilization).toBe(70);
|
|
787
793
|
});
|
|
788
794
|
|
|
@@ -792,12 +798,12 @@ describe("WorkerPool", () => {
|
|
|
792
798
|
config: { K: "V" },
|
|
793
799
|
autoscaling: { minReplicas: 1, maxReplicas: 5 },
|
|
794
800
|
});
|
|
795
|
-
expect((result.deployment.metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
796
|
-
expect((result.serviceAccount
|
|
797
|
-
expect((result.role
|
|
798
|
-
expect((result.roleBinding
|
|
799
|
-
expect((result.configMap
|
|
800
|
-
expect((result.hpa
|
|
801
|
+
expect((p(result.deployment).metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
802
|
+
expect((p(result.serviceAccount).metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
|
|
803
|
+
expect((p(result.role).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
804
|
+
expect((p(result.roleBinding).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
805
|
+
expect((p(result.configMap).metadata as any).labels["app.kubernetes.io/component"]).toBe("config");
|
|
806
|
+
expect((p(result.hpa).metadata as any).labels["app.kubernetes.io/component"]).toBe("autoscaler");
|
|
801
807
|
});
|
|
802
808
|
});
|
|
803
809
|
|
|
@@ -818,7 +824,7 @@ describe("NamespaceEnv", () => {
|
|
|
818
824
|
test("default-deny ingress NetworkPolicy created by default", () => {
|
|
819
825
|
const result = NamespaceEnv({ name: "team-alpha" });
|
|
820
826
|
expect(result.networkPolicy).toBeDefined();
|
|
821
|
-
const spec = result.networkPolicy
|
|
827
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
822
828
|
expect(spec.policyTypes).toEqual(["Ingress"]);
|
|
823
829
|
expect(spec.podSelector).toEqual({});
|
|
824
830
|
});
|
|
@@ -828,7 +834,7 @@ describe("NamespaceEnv", () => {
|
|
|
828
834
|
name: "team-alpha",
|
|
829
835
|
defaultDenyEgress: true,
|
|
830
836
|
});
|
|
831
|
-
const spec = result.networkPolicy
|
|
837
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
832
838
|
expect(spec.policyTypes).toContain("Ingress");
|
|
833
839
|
expect(spec.policyTypes).toContain("Egress");
|
|
834
840
|
});
|
|
@@ -841,7 +847,7 @@ describe("NamespaceEnv", () => {
|
|
|
841
847
|
maxPods: 50,
|
|
842
848
|
});
|
|
843
849
|
expect(result.resourceQuota).toBeDefined();
|
|
844
|
-
const spec = result.resourceQuota
|
|
850
|
+
const spec = p(result.resourceQuota).spec as any;
|
|
845
851
|
expect(spec.hard["limits.cpu"]).toBe("8");
|
|
846
852
|
expect(spec.hard["limits.memory"]).toBe("16Gi");
|
|
847
853
|
expect(spec.hard.pods).toBe("50");
|
|
@@ -849,7 +855,7 @@ describe("NamespaceEnv", () => {
|
|
|
849
855
|
|
|
850
856
|
test("ResourceQuota in correct namespace", () => {
|
|
851
857
|
const result = NamespaceEnv({ name: "team-alpha", cpuQuota: "4" });
|
|
852
|
-
const meta = result.resourceQuota
|
|
858
|
+
const meta = p(result.resourceQuota).metadata as any;
|
|
853
859
|
expect(meta.namespace).toBe("team-alpha");
|
|
854
860
|
expect(meta.name).toBe("team-alpha-quota");
|
|
855
861
|
});
|
|
@@ -863,7 +869,7 @@ describe("NamespaceEnv", () => {
|
|
|
863
869
|
defaultMemoryLimit: "512Mi",
|
|
864
870
|
});
|
|
865
871
|
expect(result.limitRange).toBeDefined();
|
|
866
|
-
const spec = result.limitRange
|
|
872
|
+
const spec = p(result.limitRange).spec as any;
|
|
867
873
|
const limit = spec.limits[0];
|
|
868
874
|
expect(limit.type).toBe("Container");
|
|
869
875
|
expect(limit.default.cpu).toBe("500m");
|
|
@@ -874,21 +880,21 @@ describe("NamespaceEnv", () => {
|
|
|
874
880
|
|
|
875
881
|
test("LimitRange in correct namespace", () => {
|
|
876
882
|
const result = NamespaceEnv({ name: "team-alpha", defaultCpuLimit: "1" });
|
|
877
|
-
const meta = result.limitRange
|
|
883
|
+
const meta = p(result.limitRange).metadata as any;
|
|
878
884
|
expect(meta.namespace).toBe("team-alpha");
|
|
879
885
|
expect(meta.name).toBe("team-alpha-limits");
|
|
880
886
|
});
|
|
881
887
|
|
|
882
888
|
test("includes common labels", () => {
|
|
883
889
|
const result = NamespaceEnv({ name: "team-alpha" });
|
|
884
|
-
const meta = result.namespace.metadata as any;
|
|
890
|
+
const meta = p(result.namespace).metadata as any;
|
|
885
891
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("team-alpha");
|
|
886
892
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
887
893
|
});
|
|
888
894
|
|
|
889
895
|
test("namespace resource has no namespace field (cluster-scoped)", () => {
|
|
890
896
|
const result = NamespaceEnv({ name: "team-alpha" });
|
|
891
|
-
const meta = result.namespace.metadata as any;
|
|
897
|
+
const meta = p(result.namespace).metadata as any;
|
|
892
898
|
expect(meta.namespace).toBeUndefined();
|
|
893
899
|
});
|
|
894
900
|
|
|
@@ -931,13 +937,13 @@ describe("NamespaceEnv", () => {
|
|
|
931
937
|
defaultDenyEgress: true,
|
|
932
938
|
});
|
|
933
939
|
expect(result.networkPolicy).toBeDefined();
|
|
934
|
-
const spec = result.networkPolicy
|
|
940
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
935
941
|
expect(spec.policyTypes).toEqual(["Egress"]);
|
|
936
942
|
});
|
|
937
943
|
|
|
938
944
|
test("NetworkPolicy namespace is the namespace name", () => {
|
|
939
945
|
const result = NamespaceEnv({ name: "team-beta", defaultDenyIngress: true });
|
|
940
|
-
const meta = result.networkPolicy
|
|
946
|
+
const meta = p(result.networkPolicy).metadata as any;
|
|
941
947
|
expect(meta.namespace).toBe("team-beta");
|
|
942
948
|
});
|
|
943
949
|
|
|
@@ -950,7 +956,7 @@ describe("NamespaceEnv", () => {
|
|
|
950
956
|
labels: { env: "staging" },
|
|
951
957
|
});
|
|
952
958
|
for (const resource of [result.namespace, result.resourceQuota!, result.limitRange!, result.networkPolicy!]) {
|
|
953
|
-
const meta = resource.metadata as any;
|
|
959
|
+
const meta = p(resource).metadata as any;
|
|
954
960
|
expect(meta.labels.env).toBe("staging");
|
|
955
961
|
}
|
|
956
962
|
});
|
|
@@ -962,10 +968,10 @@ describe("NamespaceEnv", () => {
|
|
|
962
968
|
defaultCpuRequest: "100m",
|
|
963
969
|
defaultDenyIngress: true,
|
|
964
970
|
});
|
|
965
|
-
expect((result.namespace.metadata as any).labels["app.kubernetes.io/component"]).toBe("namespace");
|
|
966
|
-
expect((result.resourceQuota
|
|
967
|
-
expect((result.limitRange
|
|
968
|
-
expect((result.networkPolicy
|
|
971
|
+
expect((p(result.namespace).metadata as any).labels["app.kubernetes.io/component"]).toBe("namespace");
|
|
972
|
+
expect((p(result.resourceQuota).metadata as any).labels["app.kubernetes.io/component"]).toBe("quota");
|
|
973
|
+
expect((p(result.limitRange).metadata as any).labels["app.kubernetes.io/component"]).toBe("limits");
|
|
974
|
+
expect((p(result.networkPolicy).metadata as any).labels["app.kubernetes.io/component"]).toBe("network-policy");
|
|
969
975
|
});
|
|
970
976
|
});
|
|
971
977
|
|
|
@@ -995,37 +1001,37 @@ describe("NodeAgent", () => {
|
|
|
995
1001
|
|
|
996
1002
|
test("uses ClusterRole/ClusterRoleBinding (not Role)", () => {
|
|
997
1003
|
const result = NodeAgent(minProps);
|
|
998
|
-
const binding = result.clusterRoleBinding as any;
|
|
1004
|
+
const binding = p(result.clusterRoleBinding) as any;
|
|
999
1005
|
expect(binding.roleRef.kind).toBe("ClusterRole");
|
|
1000
1006
|
expect(binding.roleRef.apiGroup).toBe("rbac.authorization.k8s.io");
|
|
1001
1007
|
});
|
|
1002
1008
|
|
|
1003
1009
|
test("ClusterRole/ClusterRoleBinding are cluster-scoped (no namespace)", () => {
|
|
1004
1010
|
const result = NodeAgent({ ...minProps, namespace: "monitoring" });
|
|
1005
|
-
const crMeta = result.clusterRole.metadata as any;
|
|
1006
|
-
const crbMeta = result.clusterRoleBinding.metadata as any;
|
|
1011
|
+
const crMeta = p(result.clusterRole).metadata as any;
|
|
1012
|
+
const crbMeta = p(result.clusterRoleBinding).metadata as any;
|
|
1007
1013
|
expect(crMeta.namespace).toBeUndefined();
|
|
1008
1014
|
expect(crbMeta.namespace).toBeUndefined();
|
|
1009
1015
|
});
|
|
1010
1016
|
|
|
1011
1017
|
test("namespaced resources get namespace", () => {
|
|
1012
1018
|
const result = NodeAgent({ ...minProps, namespace: "monitoring" });
|
|
1013
|
-
const dsMeta = result.daemonSet.metadata as any;
|
|
1014
|
-
const saMeta = result.serviceAccount.metadata as any;
|
|
1019
|
+
const dsMeta = p(result.daemonSet).metadata as any;
|
|
1020
|
+
const saMeta = p(result.serviceAccount).metadata as any;
|
|
1015
1021
|
expect(dsMeta.namespace).toBe("monitoring");
|
|
1016
1022
|
expect(saMeta.namespace).toBe("monitoring");
|
|
1017
1023
|
});
|
|
1018
1024
|
|
|
1019
1025
|
test("tolerateAllTaints adds Exists toleration by default", () => {
|
|
1020
1026
|
const result = NodeAgent(minProps);
|
|
1021
|
-
const spec = result.daemonSet.spec as any;
|
|
1027
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1022
1028
|
const tolerations = spec.template.spec.tolerations;
|
|
1023
1029
|
expect(tolerations).toEqual([{ operator: "Exists" }]);
|
|
1024
1030
|
});
|
|
1025
1031
|
|
|
1026
1032
|
test("tolerateAllTaints can be disabled", () => {
|
|
1027
1033
|
const result = NodeAgent({ ...minProps, tolerateAllTaints: false });
|
|
1028
|
-
const spec = result.daemonSet.spec as any;
|
|
1034
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1029
1035
|
expect(spec.template.spec.tolerations).toBeUndefined();
|
|
1030
1036
|
});
|
|
1031
1037
|
|
|
@@ -1034,7 +1040,7 @@ describe("NodeAgent", () => {
|
|
|
1034
1040
|
...minProps,
|
|
1035
1041
|
hostPaths: [{ name: "varlog", hostPath: "/var/log", mountPath: "/var/log" }],
|
|
1036
1042
|
});
|
|
1037
|
-
const spec = result.daemonSet.spec as any;
|
|
1043
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1038
1044
|
const volumes = spec.template.spec.volumes;
|
|
1039
1045
|
expect(volumes[0].name).toBe("varlog");
|
|
1040
1046
|
expect(volumes[0].hostPath.path).toBe("/var/log");
|
|
@@ -1049,7 +1055,7 @@ describe("NodeAgent", () => {
|
|
|
1049
1055
|
...minProps,
|
|
1050
1056
|
hostPaths: [{ name: "data", hostPath: "/data", mountPath: "/data", readOnly: false }],
|
|
1051
1057
|
});
|
|
1052
|
-
const spec = result.daemonSet.spec as any;
|
|
1058
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1053
1059
|
const mounts = spec.template.spec.containers[0].volumeMounts;
|
|
1054
1060
|
expect(mounts[0].readOnly).toBe(false);
|
|
1055
1061
|
});
|
|
@@ -1060,10 +1066,10 @@ describe("NodeAgent", () => {
|
|
|
1060
1066
|
config: { "fluent.conf": "some config" },
|
|
1061
1067
|
});
|
|
1062
1068
|
expect(result.configMap).toBeDefined();
|
|
1063
|
-
const data = (result.configMap as any).data;
|
|
1069
|
+
const data = (p(result.configMap) as any).data;
|
|
1064
1070
|
expect(data["fluent.conf"]).toBe("some config");
|
|
1065
1071
|
|
|
1066
|
-
const spec = result.daemonSet.spec as any;
|
|
1072
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1067
1073
|
const volumes = spec.template.spec.volumes;
|
|
1068
1074
|
const configVol = volumes.find((v: any) => v.name === "config");
|
|
1069
1075
|
expect(configVol.configMap.name).toBe("log-agent-config");
|
|
@@ -1076,7 +1082,7 @@ describe("NodeAgent", () => {
|
|
|
1076
1082
|
|
|
1077
1083
|
test("port creates metrics port on container", () => {
|
|
1078
1084
|
const result = NodeAgent({ ...minProps, port: 9100 });
|
|
1079
|
-
const spec = result.daemonSet.spec as any;
|
|
1085
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1080
1086
|
const container = spec.template.spec.containers[0];
|
|
1081
1087
|
expect(container.ports[0].containerPort).toBe(9100);
|
|
1082
1088
|
expect(container.ports[0].name).toBe("metrics");
|
|
@@ -1084,9 +1090,9 @@ describe("NodeAgent", () => {
|
|
|
1084
1090
|
|
|
1085
1091
|
test("RBAC naming convention", () => {
|
|
1086
1092
|
const result = NodeAgent(minProps);
|
|
1087
|
-
const saMeta = result.serviceAccount.metadata as any;
|
|
1088
|
-
const crMeta = result.clusterRole.metadata as any;
|
|
1089
|
-
const crbMeta = result.clusterRoleBinding.metadata as any;
|
|
1093
|
+
const saMeta = p(result.serviceAccount).metadata as any;
|
|
1094
|
+
const crMeta = p(result.clusterRole).metadata as any;
|
|
1095
|
+
const crbMeta = p(result.clusterRoleBinding).metadata as any;
|
|
1090
1096
|
expect(saMeta.name).toBe("log-agent-sa");
|
|
1091
1097
|
expect(crMeta.name).toBe("log-agent-role");
|
|
1092
1098
|
expect(crbMeta.name).toBe("log-agent-binding");
|
|
@@ -1094,14 +1100,14 @@ describe("NodeAgent", () => {
|
|
|
1094
1100
|
|
|
1095
1101
|
test("RBAC rules passed through to ClusterRole", () => {
|
|
1096
1102
|
const result = NodeAgent(minProps);
|
|
1097
|
-
const cr = result.clusterRole as any;
|
|
1103
|
+
const cr = p(result.clusterRole) as any;
|
|
1098
1104
|
expect(cr.rules[0].resources).toEqual(["pods", "namespaces"]);
|
|
1099
1105
|
});
|
|
1100
1106
|
|
|
1101
1107
|
test("includes common labels on all resources", () => {
|
|
1102
1108
|
const result = NodeAgent(minProps);
|
|
1103
1109
|
for (const resource of [result.daemonSet, result.serviceAccount, result.clusterRole, result.clusterRoleBinding]) {
|
|
1104
|
-
const meta = resource.metadata as any;
|
|
1110
|
+
const meta = p(resource).metadata as any;
|
|
1105
1111
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("log-agent");
|
|
1106
1112
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1107
1113
|
}
|
|
@@ -1112,20 +1118,20 @@ describe("NodeAgent", () => {
|
|
|
1112
1118
|
...minProps,
|
|
1113
1119
|
env: [{ name: "LOG_LEVEL", value: "info" }],
|
|
1114
1120
|
});
|
|
1115
|
-
const spec = result.daemonSet.spec as any;
|
|
1121
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1116
1122
|
const container = spec.template.spec.containers[0];
|
|
1117
1123
|
expect(container.env).toEqual([{ name: "LOG_LEVEL", value: "info" }]);
|
|
1118
1124
|
});
|
|
1119
1125
|
|
|
1120
1126
|
test("serviceAccountName on pod spec", () => {
|
|
1121
1127
|
const result = NodeAgent(minProps);
|
|
1122
|
-
const spec = result.daemonSet.spec as any;
|
|
1128
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1123
1129
|
expect(spec.template.spec.serviceAccountName).toBe("log-agent-sa");
|
|
1124
1130
|
});
|
|
1125
1131
|
|
|
1126
1132
|
test("default resource requests and limits on container", () => {
|
|
1127
1133
|
const result = NodeAgent(minProps);
|
|
1128
|
-
const spec = result.daemonSet.spec as any;
|
|
1134
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1129
1135
|
const container = spec.template.spec.containers[0];
|
|
1130
1136
|
expect(container.resources.requests.cpu).toBe("50m");
|
|
1131
1137
|
expect(container.resources.requests.memory).toBe("64Mi");
|
|
@@ -1141,7 +1147,7 @@ describe("NodeAgent", () => {
|
|
|
1141
1147
|
cpuLimit: "500m",
|
|
1142
1148
|
memoryLimit: "512Mi",
|
|
1143
1149
|
});
|
|
1144
|
-
const spec = result.daemonSet.spec as any;
|
|
1150
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1145
1151
|
const container = spec.template.spec.containers[0];
|
|
1146
1152
|
expect(container.resources.requests.cpu).toBe("100m");
|
|
1147
1153
|
expect(container.resources.requests.memory).toBe("256Mi");
|
|
@@ -1157,7 +1163,7 @@ describe("NodeAgent", () => {
|
|
|
1157
1163
|
{ name: "run", hostPath: "/run", mountPath: "/run", readOnly: false },
|
|
1158
1164
|
],
|
|
1159
1165
|
});
|
|
1160
|
-
const spec = result.daemonSet.spec as any;
|
|
1166
|
+
const spec = p(result.daemonSet).spec as any;
|
|
1161
1167
|
expect(spec.template.spec.volumes).toHaveLength(2);
|
|
1162
1168
|
expect(spec.template.spec.containers[0].volumeMounts).toHaveLength(2);
|
|
1163
1169
|
expect(spec.template.spec.containers[0].volumeMounts[1].readOnly).toBe(false);
|
|
@@ -1169,7 +1175,7 @@ describe("NodeAgent", () => {
|
|
|
1169
1175
|
config: { key: "val" },
|
|
1170
1176
|
namespace: "monitoring",
|
|
1171
1177
|
});
|
|
1172
|
-
const meta = (result.configMap as any).metadata;
|
|
1178
|
+
const meta = (p(result.configMap) as any).metadata;
|
|
1173
1179
|
expect(meta.namespace).toBe("monitoring");
|
|
1174
1180
|
});
|
|
1175
1181
|
|
|
@@ -1178,11 +1184,11 @@ describe("NodeAgent", () => {
|
|
|
1178
1184
|
...minProps,
|
|
1179
1185
|
config: { key: "val" },
|
|
1180
1186
|
});
|
|
1181
|
-
expect((result.daemonSet.metadata as any).labels["app.kubernetes.io/component"]).toBe("agent");
|
|
1182
|
-
expect((result.serviceAccount.metadata as any).labels["app.kubernetes.io/component"]).toBe("agent");
|
|
1183
|
-
expect((result.clusterRole.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
1184
|
-
expect((result.clusterRoleBinding.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
1185
|
-
expect((result.configMap
|
|
1187
|
+
expect((p(result.daemonSet).metadata as any).labels["app.kubernetes.io/component"]).toBe("agent");
|
|
1188
|
+
expect((p(result.serviceAccount).metadata as any).labels["app.kubernetes.io/component"]).toBe("agent");
|
|
1189
|
+
expect((p(result.clusterRole).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
1190
|
+
expect((p(result.clusterRoleBinding).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
1191
|
+
expect((p(result.configMap).metadata as any).labels["app.kubernetes.io/component"]).toBe("config");
|
|
1186
1192
|
});
|
|
1187
1193
|
});
|
|
1188
1194
|
|
|
@@ -1192,7 +1198,7 @@ describe("WebApp hardening", () => {
|
|
|
1192
1198
|
test("PDB created when minAvailable set", () => {
|
|
1193
1199
|
const result = WebApp({ name: "app", image: "app:1.0", minAvailable: 1 });
|
|
1194
1200
|
expect(result.pdb).toBeDefined();
|
|
1195
|
-
const spec = result.pdb
|
|
1201
|
+
const spec = p(result.pdb).spec as any;
|
|
1196
1202
|
expect(spec.minAvailable).toBe(1);
|
|
1197
1203
|
expect(spec.selector.matchLabels["app.kubernetes.io/name"]).toBe("app");
|
|
1198
1204
|
});
|
|
@@ -1208,7 +1214,7 @@ describe("WebApp hardening", () => {
|
|
|
1208
1214
|
image: "app:1.0",
|
|
1209
1215
|
initContainers: [{ name: "migrate", image: "migrate:1.0", command: ["./migrate.sh"] }],
|
|
1210
1216
|
});
|
|
1211
|
-
const spec = result.deployment.spec as any;
|
|
1217
|
+
const spec = p(result.deployment).spec as any;
|
|
1212
1218
|
expect(spec.template.spec.initContainers).toHaveLength(1);
|
|
1213
1219
|
expect(spec.template.spec.initContainers[0].name).toBe("migrate");
|
|
1214
1220
|
});
|
|
@@ -1219,7 +1225,7 @@ describe("WebApp hardening", () => {
|
|
|
1219
1225
|
image: "app:1.0",
|
|
1220
1226
|
securityContext: { runAsNonRoot: true, readOnlyRootFilesystem: true },
|
|
1221
1227
|
});
|
|
1222
|
-
const spec = result.deployment.spec as any;
|
|
1228
|
+
const spec = p(result.deployment).spec as any;
|
|
1223
1229
|
const container = spec.template.spec.containers[0];
|
|
1224
1230
|
expect(container.securityContext.runAsNonRoot).toBe(true);
|
|
1225
1231
|
});
|
|
@@ -1236,7 +1242,7 @@ describe("WebApp hardening", () => {
|
|
|
1236
1242
|
seccompProfile: { type: "RuntimeDefault" },
|
|
1237
1243
|
},
|
|
1238
1244
|
});
|
|
1239
|
-
const spec = result.deployment.spec as any;
|
|
1245
|
+
const spec = p(result.deployment).spec as any;
|
|
1240
1246
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
1241
1247
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
1242
1248
|
expect(sc.capabilities).toEqual({ drop: ["ALL"] });
|
|
@@ -1245,13 +1251,13 @@ describe("WebApp hardening", () => {
|
|
|
1245
1251
|
|
|
1246
1252
|
test("terminationGracePeriodSeconds set", () => {
|
|
1247
1253
|
const result = WebApp({ name: "app", image: "app:1.0", terminationGracePeriodSeconds: 60 });
|
|
1248
|
-
const spec = result.deployment.spec as any;
|
|
1254
|
+
const spec = p(result.deployment).spec as any;
|
|
1249
1255
|
expect(spec.template.spec.terminationGracePeriodSeconds).toBe(60);
|
|
1250
1256
|
});
|
|
1251
1257
|
|
|
1252
1258
|
test("priorityClassName set", () => {
|
|
1253
1259
|
const result = WebApp({ name: "app", image: "app:1.0", priorityClassName: "high-priority" });
|
|
1254
|
-
const spec = result.deployment.spec as any;
|
|
1260
|
+
const spec = p(result.deployment).spec as any;
|
|
1255
1261
|
expect(spec.template.spec.priorityClassName).toBe("high-priority");
|
|
1256
1262
|
});
|
|
1257
1263
|
|
|
@@ -1265,7 +1271,7 @@ describe("WebApp hardening", () => {
|
|
|
1265
1271
|
{ path: "/web", serviceName: "web", servicePort: 3000 },
|
|
1266
1272
|
],
|
|
1267
1273
|
});
|
|
1268
|
-
const spec = result.ingress
|
|
1274
|
+
const spec = p(result.ingress).spec as any;
|
|
1269
1275
|
expect(spec.rules[0].http.paths).toHaveLength(2);
|
|
1270
1276
|
expect(spec.rules[0].http.paths[0].path).toBe("/api");
|
|
1271
1277
|
expect(spec.rules[0].http.paths[1].backend.service.name).toBe("web");
|
|
@@ -1276,7 +1282,7 @@ describe("StatefulApp hardening", () => {
|
|
|
1276
1282
|
test("PDB created when minAvailable set", () => {
|
|
1277
1283
|
const result = StatefulApp({ name: "db", image: "postgres:16", minAvailable: 1 });
|
|
1278
1284
|
expect(result.pdb).toBeDefined();
|
|
1279
|
-
const spec = result.pdb
|
|
1285
|
+
const spec = p(result.pdb).spec as any;
|
|
1280
1286
|
expect(spec.minAvailable).toBe(1);
|
|
1281
1287
|
});
|
|
1282
1288
|
|
|
@@ -1286,7 +1292,7 @@ describe("StatefulApp hardening", () => {
|
|
|
1286
1292
|
image: "postgres:16",
|
|
1287
1293
|
initContainers: [{ name: "init", image: "init:1.0" }],
|
|
1288
1294
|
});
|
|
1289
|
-
const spec = result.statefulSet.spec as any;
|
|
1295
|
+
const spec = p(result.statefulSet).spec as any;
|
|
1290
1296
|
expect(spec.template.spec.initContainers).toHaveLength(1);
|
|
1291
1297
|
});
|
|
1292
1298
|
|
|
@@ -1296,7 +1302,7 @@ describe("StatefulApp hardening", () => {
|
|
|
1296
1302
|
image: "postgres:16",
|
|
1297
1303
|
securityContext: { runAsNonRoot: true },
|
|
1298
1304
|
});
|
|
1299
|
-
const spec = result.statefulSet.spec as any;
|
|
1305
|
+
const spec = p(result.statefulSet).spec as any;
|
|
1300
1306
|
expect(spec.template.spec.containers[0].securityContext.runAsNonRoot).toBe(true);
|
|
1301
1307
|
});
|
|
1302
1308
|
|
|
@@ -1311,7 +1317,7 @@ describe("StatefulApp hardening", () => {
|
|
|
1311
1317
|
seccompProfile: { type: "RuntimeDefault" },
|
|
1312
1318
|
},
|
|
1313
1319
|
});
|
|
1314
|
-
const spec = result.statefulSet.spec as any;
|
|
1320
|
+
const spec = p(result.statefulSet).spec as any;
|
|
1315
1321
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
1316
1322
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
1317
1323
|
expect(sc.capabilities).toEqual({ drop: ["ALL"] });
|
|
@@ -1323,7 +1329,7 @@ describe("WorkerPool hardening", () => {
|
|
|
1323
1329
|
test("PDB created when minAvailable set", () => {
|
|
1324
1330
|
const result = WorkerPool({ name: "w", image: "w:1.0", minAvailable: 1 });
|
|
1325
1331
|
expect(result.pdb).toBeDefined();
|
|
1326
|
-
const spec = result.pdb
|
|
1332
|
+
const spec = p(result.pdb).spec as any;
|
|
1327
1333
|
expect(spec.minAvailable).toBe(1);
|
|
1328
1334
|
});
|
|
1329
1335
|
|
|
@@ -1333,7 +1339,7 @@ describe("WorkerPool hardening", () => {
|
|
|
1333
1339
|
image: "w:1.0",
|
|
1334
1340
|
securityContext: { runAsNonRoot: true },
|
|
1335
1341
|
});
|
|
1336
|
-
const spec = result.deployment.spec as any;
|
|
1342
|
+
const spec = p(result.deployment).spec as any;
|
|
1337
1343
|
expect(spec.template.spec.containers[0].securityContext.runAsNonRoot).toBe(true);
|
|
1338
1344
|
});
|
|
1339
1345
|
|
|
@@ -1348,7 +1354,7 @@ describe("WorkerPool hardening", () => {
|
|
|
1348
1354
|
seccompProfile: { type: "RuntimeDefault" },
|
|
1349
1355
|
},
|
|
1350
1356
|
});
|
|
1351
|
-
const spec = result.deployment.spec as any;
|
|
1357
|
+
const spec = p(result.deployment).spec as any;
|
|
1352
1358
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
1353
1359
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
1354
1360
|
expect(sc.capabilities).toEqual({ drop: ["ALL"] });
|
|
@@ -1364,7 +1370,7 @@ describe("AutoscaledService hardening", () => {
|
|
|
1364
1370
|
...minProps,
|
|
1365
1371
|
initContainers: [{ name: "migrate", image: "m:1.0" }],
|
|
1366
1372
|
});
|
|
1367
|
-
const spec = result.deployment.spec as any;
|
|
1373
|
+
const spec = p(result.deployment).spec as any;
|
|
1368
1374
|
expect(spec.template.spec.initContainers).toHaveLength(1);
|
|
1369
1375
|
});
|
|
1370
1376
|
|
|
@@ -1373,7 +1379,7 @@ describe("AutoscaledService hardening", () => {
|
|
|
1373
1379
|
...minProps,
|
|
1374
1380
|
securityContext: { runAsNonRoot: true },
|
|
1375
1381
|
});
|
|
1376
|
-
const spec = result.deployment.spec as any;
|
|
1382
|
+
const spec = p(result.deployment).spec as any;
|
|
1377
1383
|
expect(spec.template.spec.containers[0].securityContext.runAsNonRoot).toBe(true);
|
|
1378
1384
|
});
|
|
1379
1385
|
|
|
@@ -1387,7 +1393,7 @@ describe("AutoscaledService hardening", () => {
|
|
|
1387
1393
|
seccompProfile: { type: "RuntimeDefault" },
|
|
1388
1394
|
},
|
|
1389
1395
|
});
|
|
1390
|
-
const spec = result.deployment.spec as any;
|
|
1396
|
+
const spec = p(result.deployment).spec as any;
|
|
1391
1397
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
1392
1398
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
1393
1399
|
expect(sc.capabilities).toEqual({ drop: ["ALL"] });
|
|
@@ -1396,7 +1402,7 @@ describe("AutoscaledService hardening", () => {
|
|
|
1396
1402
|
|
|
1397
1403
|
test("terminationGracePeriodSeconds set", () => {
|
|
1398
1404
|
const result = AutoscaledService({ ...minProps, terminationGracePeriodSeconds: 30 });
|
|
1399
|
-
const spec = result.deployment.spec as any;
|
|
1405
|
+
const spec = p(result.deployment).spec as any;
|
|
1400
1406
|
expect(spec.template.spec.terminationGracePeriodSeconds).toBe(30);
|
|
1401
1407
|
});
|
|
1402
1408
|
});
|
|
@@ -1416,20 +1422,20 @@ describe("BatchJob", () => {
|
|
|
1416
1422
|
|
|
1417
1423
|
test("default backoffLimit is 6", () => {
|
|
1418
1424
|
const result = BatchJob(minProps);
|
|
1419
|
-
const spec = result.job.spec as any;
|
|
1425
|
+
const spec = p(result.job).spec as any;
|
|
1420
1426
|
expect(spec.backoffLimit).toBe(6);
|
|
1421
1427
|
});
|
|
1422
1428
|
|
|
1423
1429
|
test("custom backoffLimit and ttl", () => {
|
|
1424
1430
|
const result = BatchJob({ ...minProps, backoffLimit: 3, ttlSecondsAfterFinished: 3600 });
|
|
1425
|
-
const spec = result.job.spec as any;
|
|
1431
|
+
const spec = p(result.job).spec as any;
|
|
1426
1432
|
expect(spec.backoffLimit).toBe(3);
|
|
1427
1433
|
expect(spec.ttlSecondsAfterFinished).toBe(3600);
|
|
1428
1434
|
});
|
|
1429
1435
|
|
|
1430
1436
|
test("parallelism and completions", () => {
|
|
1431
1437
|
const result = BatchJob({ ...minProps, parallelism: 3, completions: 10 });
|
|
1432
|
-
const spec = result.job.spec as any;
|
|
1438
|
+
const spec = p(result.job).spec as any;
|
|
1433
1439
|
expect(spec.parallelism).toBe(3);
|
|
1434
1440
|
expect(spec.completions).toBe(10);
|
|
1435
1441
|
});
|
|
@@ -1443,7 +1449,7 @@ describe("BatchJob", () => {
|
|
|
1443
1449
|
|
|
1444
1450
|
test("command and args passed through", () => {
|
|
1445
1451
|
const result = BatchJob({ ...minProps, command: ["python"], args: ["migrate.py"] });
|
|
1446
|
-
const spec = result.job.spec as any;
|
|
1452
|
+
const spec = p(result.job).spec as any;
|
|
1447
1453
|
const container = spec.template.spec.containers[0];
|
|
1448
1454
|
expect(container.command).toEqual(["python"]);
|
|
1449
1455
|
expect(container.args).toEqual(["migrate.py"]);
|
|
@@ -1451,14 +1457,14 @@ describe("BatchJob", () => {
|
|
|
1451
1457
|
|
|
1452
1458
|
test("namespace propagated", () => {
|
|
1453
1459
|
const result = BatchJob({ ...minProps, namespace: "jobs" });
|
|
1454
|
-
expect((result.job.metadata as any).namespace).toBe("jobs");
|
|
1455
|
-
expect((result.serviceAccount
|
|
1460
|
+
expect((p(result.job).metadata as any).namespace).toBe("jobs");
|
|
1461
|
+
expect((p(result.serviceAccount).metadata as any).namespace).toBe("jobs");
|
|
1456
1462
|
});
|
|
1457
1463
|
|
|
1458
1464
|
test("component labels", () => {
|
|
1459
1465
|
const result = BatchJob(minProps);
|
|
1460
|
-
expect((result.job.metadata as any).labels["app.kubernetes.io/component"]).toBe("batch");
|
|
1461
|
-
expect((result.role
|
|
1466
|
+
expect((p(result.job).metadata as any).labels["app.kubernetes.io/component"]).toBe("batch");
|
|
1467
|
+
expect((p(result.role).metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
|
|
1462
1468
|
});
|
|
1463
1469
|
});
|
|
1464
1470
|
|
|
@@ -1479,14 +1485,14 @@ describe("SecureIngress", () => {
|
|
|
1479
1485
|
test("creates certificate when clusterIssuer set", () => {
|
|
1480
1486
|
const result = SecureIngress({ ...minProps, clusterIssuer: "letsencrypt-prod" });
|
|
1481
1487
|
expect(result.certificate).toBeDefined();
|
|
1482
|
-
const certSpec = result.certificate
|
|
1488
|
+
const certSpec = p(result.certificate!).spec as any;
|
|
1483
1489
|
expect(certSpec.issuerRef.name).toBe("letsencrypt-prod");
|
|
1484
1490
|
expect(certSpec.dnsNames).toEqual(["app.example.com"]);
|
|
1485
1491
|
});
|
|
1486
1492
|
|
|
1487
1493
|
test("TLS on ingress when clusterIssuer set", () => {
|
|
1488
1494
|
const result = SecureIngress({ ...minProps, clusterIssuer: "letsencrypt-prod" });
|
|
1489
|
-
const spec = result.ingress.spec as any;
|
|
1495
|
+
const spec = p(result.ingress).spec as any;
|
|
1490
1496
|
expect(spec.tls).toBeDefined();
|
|
1491
1497
|
expect(spec.tls[0].hosts).toEqual(["app.example.com"]);
|
|
1492
1498
|
});
|
|
@@ -1500,9 +1506,9 @@ describe("SecureIngress", () => {
|
|
|
1500
1506
|
],
|
|
1501
1507
|
clusterIssuer: "letsencrypt-prod",
|
|
1502
1508
|
});
|
|
1503
|
-
const spec = result.ingress.spec as any;
|
|
1509
|
+
const spec = p(result.ingress).spec as any;
|
|
1504
1510
|
expect(spec.rules).toHaveLength(2);
|
|
1505
|
-
const certSpec = result.certificate
|
|
1511
|
+
const certSpec = p(result.certificate!).spec as any;
|
|
1506
1512
|
expect(certSpec.dnsNames).toHaveLength(2);
|
|
1507
1513
|
});
|
|
1508
1514
|
|
|
@@ -1517,19 +1523,19 @@ describe("SecureIngress", () => {
|
|
|
1517
1523
|
],
|
|
1518
1524
|
}],
|
|
1519
1525
|
});
|
|
1520
|
-
const spec = result.ingress.spec as any;
|
|
1526
|
+
const spec = p(result.ingress).spec as any;
|
|
1521
1527
|
expect(spec.rules[0].http.paths).toHaveLength(2);
|
|
1522
1528
|
});
|
|
1523
1529
|
|
|
1524
1530
|
test("ingressClassName set", () => {
|
|
1525
1531
|
const result = SecureIngress({ ...minProps, ingressClassName: "nginx" });
|
|
1526
|
-
const spec = result.ingress.spec as any;
|
|
1532
|
+
const spec = p(result.ingress).spec as any;
|
|
1527
1533
|
expect(spec.ingressClassName).toBe("nginx");
|
|
1528
1534
|
});
|
|
1529
1535
|
|
|
1530
1536
|
test("cert-manager annotation added", () => {
|
|
1531
1537
|
const result = SecureIngress({ ...minProps, clusterIssuer: "letsencrypt-prod" });
|
|
1532
|
-
const meta = result.ingress.metadata as any;
|
|
1538
|
+
const meta = p(result.ingress).metadata as any;
|
|
1533
1539
|
expect(meta.annotations["cert-manager.io/cluster-issuer"]).toBe("letsencrypt-prod");
|
|
1534
1540
|
});
|
|
1535
1541
|
});
|
|
@@ -1549,12 +1555,12 @@ describe("ConfiguredApp", () => {
|
|
|
1549
1555
|
test("creates ConfigMap when configData provided", () => {
|
|
1550
1556
|
const result = ConfiguredApp({ ...minProps, configData: { "app.conf": "key=val" }, configMountPath: "/etc/app" });
|
|
1551
1557
|
expect(result.configMap).toBeDefined();
|
|
1552
|
-
expect((result.configMap as any).data["app.conf"]).toBe("key=val");
|
|
1558
|
+
expect((p(result.configMap) as any).data["app.conf"]).toBe("key=val");
|
|
1553
1559
|
});
|
|
1554
1560
|
|
|
1555
1561
|
test("configMap volume mounted", () => {
|
|
1556
1562
|
const result = ConfiguredApp({ ...minProps, configData: { "k": "v" }, configMountPath: "/etc/app" });
|
|
1557
|
-
const spec = result.deployment.spec as any;
|
|
1563
|
+
const spec = p(result.deployment).spec as any;
|
|
1558
1564
|
const container = spec.template.spec.containers[0];
|
|
1559
1565
|
expect(container.volumeMounts).toHaveLength(1);
|
|
1560
1566
|
expect(container.volumeMounts[0].mountPath).toBe("/etc/app");
|
|
@@ -1563,7 +1569,7 @@ describe("ConfiguredApp", () => {
|
|
|
1563
1569
|
|
|
1564
1570
|
test("secret volume mounted", () => {
|
|
1565
1571
|
const result = ConfiguredApp({ ...minProps, secretName: "creds", secretMountPath: "/secrets" });
|
|
1566
|
-
const spec = result.deployment.spec as any;
|
|
1572
|
+
const spec = p(result.deployment).spec as any;
|
|
1567
1573
|
const container = spec.template.spec.containers[0];
|
|
1568
1574
|
expect(container.volumeMounts[0].mountPath).toBe("/secrets");
|
|
1569
1575
|
expect(spec.template.spec.volumes[0].secret.secretName).toBe("creds");
|
|
@@ -1574,7 +1580,7 @@ describe("ConfiguredApp", () => {
|
|
|
1574
1580
|
...minProps,
|
|
1575
1581
|
envFrom: { configMapRef: "my-config", secretRef: "my-secret" },
|
|
1576
1582
|
});
|
|
1577
|
-
const spec = result.deployment.spec as any;
|
|
1583
|
+
const spec = p(result.deployment).spec as any;
|
|
1578
1584
|
const container = spec.template.spec.containers[0];
|
|
1579
1585
|
expect(container.envFrom).toHaveLength(2);
|
|
1580
1586
|
expect(container.envFrom[0].configMapRef.name).toBe("my-config");
|
|
@@ -1586,14 +1592,14 @@ describe("ConfiguredApp", () => {
|
|
|
1586
1592
|
...minProps,
|
|
1587
1593
|
initContainers: [{ name: "init", image: "init:1.0", command: ["sh"] }],
|
|
1588
1594
|
});
|
|
1589
|
-
const spec = result.deployment.spec as any;
|
|
1595
|
+
const spec = p(result.deployment).spec as any;
|
|
1590
1596
|
expect(spec.template.spec.initContainers).toHaveLength(1);
|
|
1591
1597
|
});
|
|
1592
1598
|
|
|
1593
1599
|
test("namespace propagated", () => {
|
|
1594
1600
|
const result = ConfiguredApp({ ...minProps, namespace: "prod" });
|
|
1595
|
-
expect((result.deployment.metadata as any).namespace).toBe("prod");
|
|
1596
|
-
expect((result.service.metadata as any).namespace).toBe("prod");
|
|
1601
|
+
expect((p(result.deployment).metadata as any).namespace).toBe("prod");
|
|
1602
|
+
expect((p(result.service).metadata as any).namespace).toBe("prod");
|
|
1597
1603
|
});
|
|
1598
1604
|
});
|
|
1599
1605
|
|
|
@@ -1614,7 +1620,7 @@ describe("SidecarApp", () => {
|
|
|
1614
1620
|
|
|
1615
1621
|
test("has multiple containers", () => {
|
|
1616
1622
|
const result = SidecarApp(minProps);
|
|
1617
|
-
const spec = result.deployment.spec as any;
|
|
1623
|
+
const spec = p(result.deployment).spec as any;
|
|
1618
1624
|
expect(spec.template.spec.containers).toHaveLength(2);
|
|
1619
1625
|
expect(spec.template.spec.containers[0].name).toBe("api");
|
|
1620
1626
|
expect(spec.template.spec.containers[1].name).toBe("envoy");
|
|
@@ -1625,7 +1631,7 @@ describe("SidecarApp", () => {
|
|
|
1625
1631
|
...minProps,
|
|
1626
1632
|
sidecars: [{ name: "envoy", image: "envoy:v1.28", ports: [{ containerPort: 9901, name: "admin" }] }],
|
|
1627
1633
|
});
|
|
1628
|
-
const spec = result.deployment.spec as any;
|
|
1634
|
+
const spec = p(result.deployment).spec as any;
|
|
1629
1635
|
expect(spec.template.spec.containers[1].ports[0].containerPort).toBe(9901);
|
|
1630
1636
|
});
|
|
1631
1637
|
|
|
@@ -1634,7 +1640,7 @@ describe("SidecarApp", () => {
|
|
|
1634
1640
|
...minProps,
|
|
1635
1641
|
initContainers: [{ name: "migrate", image: "m:1.0", command: ["./migrate.sh"] }],
|
|
1636
1642
|
});
|
|
1637
|
-
const spec = result.deployment.spec as any;
|
|
1643
|
+
const spec = p(result.deployment).spec as any;
|
|
1638
1644
|
expect(spec.template.spec.initContainers).toHaveLength(1);
|
|
1639
1645
|
});
|
|
1640
1646
|
|
|
@@ -1643,7 +1649,7 @@ describe("SidecarApp", () => {
|
|
|
1643
1649
|
...minProps,
|
|
1644
1650
|
sharedVolumes: [{ name: "tmp" }, { name: "config", configMapName: "my-config" }],
|
|
1645
1651
|
});
|
|
1646
|
-
const spec = result.deployment.spec as any;
|
|
1652
|
+
const spec = p(result.deployment).spec as any;
|
|
1647
1653
|
expect(spec.template.spec.volumes).toHaveLength(2);
|
|
1648
1654
|
expect(spec.template.spec.volumes[0].emptyDir).toBeDefined();
|
|
1649
1655
|
expect(spec.template.spec.volumes[1].configMap.name).toBe("my-config");
|
|
@@ -1651,8 +1657,8 @@ describe("SidecarApp", () => {
|
|
|
1651
1657
|
|
|
1652
1658
|
test("common labels on all resources", () => {
|
|
1653
1659
|
const result = SidecarApp(minProps);
|
|
1654
|
-
expect((result.deployment.metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1655
|
-
expect((result.service.metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1660
|
+
expect((p(result.deployment).metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1661
|
+
expect((p(result.service).metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1656
1662
|
});
|
|
1657
1663
|
});
|
|
1658
1664
|
|
|
@@ -1675,14 +1681,14 @@ describe("MonitoredService", () => {
|
|
|
1675
1681
|
alertRules: [{ name: "HighError", expr: "rate(errors[5m]) > 0.1", severity: "critical" }],
|
|
1676
1682
|
});
|
|
1677
1683
|
expect(result.prometheusRule).toBeDefined();
|
|
1678
|
-
const spec = result.prometheusRule
|
|
1684
|
+
const spec = p(result.prometheusRule!).spec as any;
|
|
1679
1685
|
expect(spec.groups[0].rules[0].alert).toBe("HighError");
|
|
1680
1686
|
expect(spec.groups[0].rules[0].labels.severity).toBe("critical");
|
|
1681
1687
|
});
|
|
1682
1688
|
|
|
1683
1689
|
test("serviceMonitor has correct selector and endpoint", () => {
|
|
1684
1690
|
const result = MonitoredService({ ...minProps, metricsPort: 9090, metricsPath: "/metrics", scrapeInterval: "15s" });
|
|
1685
|
-
const spec = result.serviceMonitor.spec as any;
|
|
1691
|
+
const spec = p(result.serviceMonitor).spec as any;
|
|
1686
1692
|
expect(spec.selector.matchLabels["app.kubernetes.io/name"]).toBe("api");
|
|
1687
1693
|
expect(spec.endpoints[0].port).toBe("metrics");
|
|
1688
1694
|
expect(spec.endpoints[0].path).toBe("/metrics");
|
|
@@ -1691,7 +1697,7 @@ describe("MonitoredService", () => {
|
|
|
1691
1697
|
|
|
1692
1698
|
test("separate metrics port on container and service", () => {
|
|
1693
1699
|
const result = MonitoredService({ ...minProps, port: 8080, metricsPort: 9090 });
|
|
1694
|
-
const spec = result.deployment.spec as any;
|
|
1700
|
+
const spec = p(result.deployment).spec as any;
|
|
1695
1701
|
const ports = spec.template.spec.containers[0].ports;
|
|
1696
1702
|
expect(ports).toHaveLength(2);
|
|
1697
1703
|
expect(ports[0].containerPort).toBe(8080);
|
|
@@ -1700,8 +1706,8 @@ describe("MonitoredService", () => {
|
|
|
1700
1706
|
|
|
1701
1707
|
test("component labels", () => {
|
|
1702
1708
|
const result = MonitoredService({ ...minProps, alertRules: [{ name: "A", expr: "1" }] });
|
|
1703
|
-
expect((result.serviceMonitor.metadata as any).labels["app.kubernetes.io/component"]).toBe("monitoring");
|
|
1704
|
-
expect((result.prometheusRule
|
|
1709
|
+
expect((p(result.serviceMonitor).metadata as any).labels["app.kubernetes.io/component"]).toBe("monitoring");
|
|
1710
|
+
expect((p(result.prometheusRule!).metadata as any).labels["app.kubernetes.io/component"]).toBe("monitoring");
|
|
1705
1711
|
});
|
|
1706
1712
|
});
|
|
1707
1713
|
|
|
@@ -1719,7 +1725,7 @@ describe("NetworkIsolatedApp", () => {
|
|
|
1719
1725
|
|
|
1720
1726
|
test("networkPolicy podSelector matches app", () => {
|
|
1721
1727
|
const result = NetworkIsolatedApp(minProps);
|
|
1722
|
-
const spec = result.networkPolicy.spec as any;
|
|
1728
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
1723
1729
|
expect(spec.podSelector.matchLabels["app.kubernetes.io/name"]).toBe("api");
|
|
1724
1730
|
});
|
|
1725
1731
|
|
|
@@ -1728,7 +1734,7 @@ describe("NetworkIsolatedApp", () => {
|
|
|
1728
1734
|
...minProps,
|
|
1729
1735
|
allowIngressFrom: [{ podSelector: { "app.kubernetes.io/name": "frontend" } }],
|
|
1730
1736
|
});
|
|
1731
|
-
const spec = result.networkPolicy.spec as any;
|
|
1737
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
1732
1738
|
expect(spec.policyTypes).toContain("Ingress");
|
|
1733
1739
|
expect(spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/name"]).toBe("frontend");
|
|
1734
1740
|
});
|
|
@@ -1738,19 +1744,19 @@ describe("NetworkIsolatedApp", () => {
|
|
|
1738
1744
|
...minProps,
|
|
1739
1745
|
allowEgressTo: [{ podSelector: { "app.kubernetes.io/name": "db" }, ports: [{ port: 5432 }] }],
|
|
1740
1746
|
});
|
|
1741
|
-
const spec = result.networkPolicy.spec as any;
|
|
1747
|
+
const spec = p(result.networkPolicy).spec as any;
|
|
1742
1748
|
expect(spec.policyTypes).toContain("Egress");
|
|
1743
1749
|
expect(spec.egress[0].ports[0].port).toBe(5432);
|
|
1744
1750
|
});
|
|
1745
1751
|
|
|
1746
1752
|
test("namespace propagated", () => {
|
|
1747
1753
|
const result = NetworkIsolatedApp({ ...minProps, namespace: "prod" });
|
|
1748
|
-
expect((result.networkPolicy.metadata as any).namespace).toBe("prod");
|
|
1754
|
+
expect((p(result.networkPolicy).metadata as any).namespace).toBe("prod");
|
|
1749
1755
|
});
|
|
1750
1756
|
|
|
1751
1757
|
test("component label on networkPolicy", () => {
|
|
1752
1758
|
const result = NetworkIsolatedApp(minProps);
|
|
1753
|
-
expect((result.networkPolicy.metadata as any).labels["app.kubernetes.io/component"]).toBe("network-policy");
|
|
1759
|
+
expect((p(result.networkPolicy).metadata as any).labels["app.kubernetes.io/component"]).toBe("network-policy");
|
|
1754
1760
|
});
|
|
1755
1761
|
});
|
|
1756
1762
|
|
|
@@ -1762,7 +1768,7 @@ describe("IrsaServiceAccount", () => {
|
|
|
1762
1768
|
test("returns serviceAccount with IRSA annotation", () => {
|
|
1763
1769
|
const result = IrsaServiceAccount(minProps);
|
|
1764
1770
|
expect(result.serviceAccount).toBeDefined();
|
|
1765
|
-
const meta = result.serviceAccount.metadata as any;
|
|
1771
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
1766
1772
|
expect(meta.annotations["eks.amazonaws.com/role-arn"]).toBe("arn:aws:iam::123456789012:role/app-role");
|
|
1767
1773
|
});
|
|
1768
1774
|
|
|
@@ -1779,18 +1785,18 @@ describe("IrsaServiceAccount", () => {
|
|
|
1779
1785
|
});
|
|
1780
1786
|
expect(result.role).toBeDefined();
|
|
1781
1787
|
expect(result.roleBinding).toBeDefined();
|
|
1782
|
-
const role = result.role as any;
|
|
1788
|
+
const role = p(result.role) as any;
|
|
1783
1789
|
expect(role.rules[0].resources).toEqual(["secrets"]);
|
|
1784
1790
|
});
|
|
1785
1791
|
|
|
1786
1792
|
test("namespace propagated", () => {
|
|
1787
1793
|
const result = IrsaServiceAccount({ ...minProps, namespace: "prod" });
|
|
1788
|
-
expect((result.serviceAccount.metadata as any).namespace).toBe("prod");
|
|
1794
|
+
expect((p(result.serviceAccount).metadata as any).namespace).toBe("prod");
|
|
1789
1795
|
});
|
|
1790
1796
|
|
|
1791
1797
|
test("component labels", () => {
|
|
1792
1798
|
const result = IrsaServiceAccount(minProps);
|
|
1793
|
-
expect((result.serviceAccount.metadata as any).labels["app.kubernetes.io/component"]).toBe("service-account");
|
|
1799
|
+
expect((p(result.serviceAccount).metadata as any).labels["app.kubernetes.io/component"]).toBe("service-account");
|
|
1794
1800
|
});
|
|
1795
1801
|
});
|
|
1796
1802
|
|
|
@@ -1805,58 +1811,58 @@ describe("AlbIngress", () => {
|
|
|
1805
1811
|
test("returns ingress with ALB annotations", () => {
|
|
1806
1812
|
const result = AlbIngress(minProps);
|
|
1807
1813
|
expect(result.ingress).toBeDefined();
|
|
1808
|
-
const meta = result.ingress.metadata as any;
|
|
1814
|
+
const meta = p(result.ingress).metadata as any;
|
|
1809
1815
|
expect(meta.annotations["alb.ingress.kubernetes.io/scheme"]).toBe("internet-facing");
|
|
1810
1816
|
expect(meta.annotations["alb.ingress.kubernetes.io/target-type"]).toBe("ip");
|
|
1811
1817
|
});
|
|
1812
1818
|
|
|
1813
1819
|
test("ingressClassName is alb", () => {
|
|
1814
1820
|
const result = AlbIngress(minProps);
|
|
1815
|
-
const spec = result.ingress.spec as any;
|
|
1821
|
+
const spec = p(result.ingress).spec as any;
|
|
1816
1822
|
expect(spec.ingressClassName).toBe("alb");
|
|
1817
1823
|
});
|
|
1818
1824
|
|
|
1819
1825
|
test("certificate ARN sets TLS annotations", () => {
|
|
1820
1826
|
const result = AlbIngress({ ...minProps, certificateArn: "arn:aws:acm:us-east-1:123:cert/abc" });
|
|
1821
|
-
const meta = result.ingress.metadata as any;
|
|
1827
|
+
const meta = p(result.ingress).metadata as any;
|
|
1822
1828
|
expect(meta.annotations["alb.ingress.kubernetes.io/certificate-arn"]).toBe("arn:aws:acm:us-east-1:123:cert/abc");
|
|
1823
1829
|
expect(meta.annotations["alb.ingress.kubernetes.io/ssl-redirect"]).toBe("443");
|
|
1824
1830
|
});
|
|
1825
1831
|
|
|
1826
1832
|
test("groupName annotation set", () => {
|
|
1827
1833
|
const result = AlbIngress({ ...minProps, groupName: "shared-alb" });
|
|
1828
|
-
const meta = result.ingress.metadata as any;
|
|
1834
|
+
const meta = p(result.ingress).metadata as any;
|
|
1829
1835
|
expect(meta.annotations["alb.ingress.kubernetes.io/group.name"]).toBe("shared-alb");
|
|
1830
1836
|
});
|
|
1831
1837
|
|
|
1832
1838
|
test("WAF ACL annotation set", () => {
|
|
1833
1839
|
const result = AlbIngress({ ...minProps, wafAclArn: "arn:aws:wafv2:us-east-1:123:regional/webacl/abc" });
|
|
1834
|
-
const meta = result.ingress.metadata as any;
|
|
1840
|
+
const meta = p(result.ingress).metadata as any;
|
|
1835
1841
|
expect(meta.annotations["alb.ingress.kubernetes.io/wafv2-acl-arn"]).toBe("arn:aws:wafv2:us-east-1:123:regional/webacl/abc");
|
|
1836
1842
|
});
|
|
1837
1843
|
|
|
1838
1844
|
test("internal scheme", () => {
|
|
1839
1845
|
const result = AlbIngress({ ...minProps, scheme: "internal" });
|
|
1840
|
-
const meta = result.ingress.metadata as any;
|
|
1846
|
+
const meta = p(result.ingress).metadata as any;
|
|
1841
1847
|
expect(meta.annotations["alb.ingress.kubernetes.io/scheme"]).toBe("internal");
|
|
1842
1848
|
});
|
|
1843
1849
|
|
|
1844
1850
|
test("empty-string certificateArn does NOT set ssl-redirect", () => {
|
|
1845
1851
|
const result = AlbIngress({ ...minProps, certificateArn: "" });
|
|
1846
|
-
const meta = result.ingress.metadata as any;
|
|
1852
|
+
const meta = p(result.ingress).metadata as any;
|
|
1847
1853
|
expect(meta.annotations["alb.ingress.kubernetes.io/ssl-redirect"]).toBeUndefined();
|
|
1848
1854
|
expect(meta.annotations["alb.ingress.kubernetes.io/certificate-arn"]).toBeUndefined();
|
|
1849
1855
|
});
|
|
1850
1856
|
|
|
1851
1857
|
test("explicit sslRedirect: true overrides empty certificateArn", () => {
|
|
1852
1858
|
const result = AlbIngress({ ...minProps, certificateArn: "", sslRedirect: true });
|
|
1853
|
-
const meta = result.ingress.metadata as any;
|
|
1859
|
+
const meta = p(result.ingress).metadata as any;
|
|
1854
1860
|
expect(meta.annotations["alb.ingress.kubernetes.io/ssl-redirect"]).toBe("443");
|
|
1855
1861
|
});
|
|
1856
1862
|
|
|
1857
1863
|
test("explicit sslRedirect: false suppresses redirect even with cert", () => {
|
|
1858
1864
|
const result = AlbIngress({ ...minProps, certificateArn: "arn:aws:acm:us-east-1:123:cert/abc", sslRedirect: false });
|
|
1859
|
-
const meta = result.ingress.metadata as any;
|
|
1865
|
+
const meta = p(result.ingress).metadata as any;
|
|
1860
1866
|
expect(meta.annotations["alb.ingress.kubernetes.io/ssl-redirect"]).toBeUndefined();
|
|
1861
1867
|
});
|
|
1862
1868
|
});
|
|
@@ -1872,55 +1878,55 @@ describe("GceIngress", () => {
|
|
|
1872
1878
|
test("returns ingress with GCE annotations", () => {
|
|
1873
1879
|
const result = GceIngress(minProps);
|
|
1874
1880
|
expect(result.ingress).toBeDefined();
|
|
1875
|
-
const meta = result.ingress.metadata as any;
|
|
1881
|
+
const meta = p(result.ingress).metadata as any;
|
|
1876
1882
|
expect(meta.annotations["kubernetes.io/ingress.class"]).toBe("gce");
|
|
1877
1883
|
});
|
|
1878
1884
|
|
|
1879
1885
|
test("static IP annotation set", () => {
|
|
1880
1886
|
const result = GceIngress({ ...minProps, staticIpName: "microservice-ip" });
|
|
1881
|
-
const meta = result.ingress.metadata as any;
|
|
1887
|
+
const meta = p(result.ingress).metadata as any;
|
|
1882
1888
|
expect(meta.annotations["kubernetes.io/ingress.global-static-ip-name"]).toBe("microservice-ip");
|
|
1883
1889
|
});
|
|
1884
1890
|
|
|
1885
1891
|
test("managed certificate annotation set", () => {
|
|
1886
1892
|
const result = GceIngress({ ...minProps, managedCertificate: "api-cert" });
|
|
1887
|
-
const meta = result.ingress.metadata as any;
|
|
1893
|
+
const meta = p(result.ingress).metadata as any;
|
|
1888
1894
|
expect(meta.annotations["networking.gke.io/managed-certificates"]).toBe("api-cert");
|
|
1889
1895
|
});
|
|
1890
1896
|
|
|
1891
1897
|
test("frontendConfig annotation set", () => {
|
|
1892
1898
|
const result = GceIngress({ ...minProps, frontendConfig: "my-frontend" });
|
|
1893
|
-
const meta = result.ingress.metadata as any;
|
|
1899
|
+
const meta = p(result.ingress).metadata as any;
|
|
1894
1900
|
expect(meta.annotations["networking.gke.io/v1beta1.FrontendConfig"]).toBe("my-frontend");
|
|
1895
1901
|
});
|
|
1896
1902
|
|
|
1897
1903
|
test("managedCertificate sets default FrontendConfig for ssl redirect", () => {
|
|
1898
1904
|
const result = GceIngress({ ...minProps, managedCertificate: "api-cert" });
|
|
1899
|
-
const meta = result.ingress.metadata as any;
|
|
1905
|
+
const meta = p(result.ingress).metadata as any;
|
|
1900
1906
|
expect(meta.annotations["networking.gke.io/v1beta1.FrontendConfig"]).toBe("api-ingress-frontend-config");
|
|
1901
1907
|
});
|
|
1902
1908
|
|
|
1903
1909
|
test("explicit sslRedirect: false suppresses FrontendConfig even with cert", () => {
|
|
1904
1910
|
const result = GceIngress({ ...minProps, managedCertificate: "api-cert", sslRedirect: false });
|
|
1905
|
-
const meta = result.ingress.metadata as any;
|
|
1911
|
+
const meta = p(result.ingress).metadata as any;
|
|
1906
1912
|
expect(meta.annotations["networking.gke.io/v1beta1.FrontendConfig"]).toBeUndefined();
|
|
1907
1913
|
});
|
|
1908
1914
|
|
|
1909
1915
|
test("explicit sslRedirect: true sets default FrontendConfig without cert", () => {
|
|
1910
1916
|
const result = GceIngress({ ...minProps, sslRedirect: true });
|
|
1911
|
-
const meta = result.ingress.metadata as any;
|
|
1917
|
+
const meta = p(result.ingress).metadata as any;
|
|
1912
1918
|
expect(meta.annotations["networking.gke.io/v1beta1.FrontendConfig"]).toBe("api-ingress-frontend-config");
|
|
1913
1919
|
});
|
|
1914
1920
|
|
|
1915
1921
|
test("namespace is set when provided", () => {
|
|
1916
1922
|
const result = GceIngress({ ...minProps, namespace: "production" });
|
|
1917
|
-
const meta = result.ingress.metadata as any;
|
|
1923
|
+
const meta = p(result.ingress).metadata as any;
|
|
1918
1924
|
expect(meta.namespace).toBe("production");
|
|
1919
1925
|
});
|
|
1920
1926
|
|
|
1921
1927
|
test("host rules are mapped correctly", () => {
|
|
1922
1928
|
const result = GceIngress(minProps);
|
|
1923
|
-
const spec = result.ingress.spec as any;
|
|
1929
|
+
const spec = p(result.ingress).spec as any;
|
|
1924
1930
|
expect(spec.rules).toHaveLength(1);
|
|
1925
1931
|
expect(spec.rules[0].host).toBe("api.example.com");
|
|
1926
1932
|
expect(spec.rules[0].http.paths[0].backend.service.name).toBe("api");
|
|
@@ -1928,7 +1934,7 @@ describe("GceIngress", () => {
|
|
|
1928
1934
|
|
|
1929
1935
|
test("labels include component: ingress", () => {
|
|
1930
1936
|
const result = GceIngress(minProps);
|
|
1931
|
-
const meta = result.ingress.metadata as any;
|
|
1937
|
+
const meta = p(result.ingress).metadata as any;
|
|
1932
1938
|
expect(meta.labels["app.kubernetes.io/component"]).toBe("ingress");
|
|
1933
1939
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
1934
1940
|
});
|
|
@@ -1940,22 +1946,22 @@ describe("EbsStorageClass", () => {
|
|
|
1940
1946
|
test("returns storageClass with EBS provisioner", () => {
|
|
1941
1947
|
const result = EbsStorageClass({ name: "gp3" });
|
|
1942
1948
|
expect(result.storageClass).toBeDefined();
|
|
1943
|
-
expect((result.storageClass as any).provisioner).toBe("ebs.csi.aws.com");
|
|
1949
|
+
expect((p(result.storageClass) as any).provisioner).toBe("ebs.csi.aws.com");
|
|
1944
1950
|
});
|
|
1945
1951
|
|
|
1946
1952
|
test("default type is gp3", () => {
|
|
1947
1953
|
const result = EbsStorageClass({ name: "default" });
|
|
1948
|
-
expect((result.storageClass as any).parameters.type).toBe("gp3");
|
|
1954
|
+
expect((p(result.storageClass) as any).parameters.type).toBe("gp3");
|
|
1949
1955
|
});
|
|
1950
1956
|
|
|
1951
1957
|
test("encryption enabled by default", () => {
|
|
1952
1958
|
const result = EbsStorageClass({ name: "enc" });
|
|
1953
|
-
expect((result.storageClass as any).parameters.encrypted).toBe("true");
|
|
1959
|
+
expect((p(result.storageClass) as any).parameters.encrypted).toBe("true");
|
|
1954
1960
|
});
|
|
1955
1961
|
|
|
1956
1962
|
test("custom parameters", () => {
|
|
1957
1963
|
const result = EbsStorageClass({ name: "custom", type: "io2", iops: "5000", throughput: "250" });
|
|
1958
|
-
const params = (result.storageClass as any).parameters;
|
|
1964
|
+
const params = (p(result.storageClass) as any).parameters;
|
|
1959
1965
|
expect(params.type).toBe("io2");
|
|
1960
1966
|
expect(params.iops).toBe("5000");
|
|
1961
1967
|
expect(params.throughput).toBe("250");
|
|
@@ -1963,24 +1969,24 @@ describe("EbsStorageClass", () => {
|
|
|
1963
1969
|
|
|
1964
1970
|
test("allowVolumeExpansion default true", () => {
|
|
1965
1971
|
const result = EbsStorageClass({ name: "exp" });
|
|
1966
|
-
expect((result.storageClass as any).allowVolumeExpansion).toBe(true);
|
|
1972
|
+
expect((p(result.storageClass) as any).allowVolumeExpansion).toBe(true);
|
|
1967
1973
|
});
|
|
1968
1974
|
|
|
1969
1975
|
test("storageClass is cluster-scoped (no namespace)", () => {
|
|
1970
1976
|
const result = EbsStorageClass({ name: "sc" });
|
|
1971
|
-
expect((result.storageClass.metadata as any).namespace).toBeUndefined();
|
|
1977
|
+
expect((p(result.storageClass).metadata as any).namespace).toBeUndefined();
|
|
1972
1978
|
});
|
|
1973
1979
|
|
|
1974
1980
|
test("numeric iops and throughput coerced to strings", () => {
|
|
1975
1981
|
const result = EbsStorageClass({ name: "perf", iops: 5000, throughput: 250 });
|
|
1976
|
-
const params = (result.storageClass as any).parameters;
|
|
1982
|
+
const params = (p(result.storageClass) as any).parameters;
|
|
1977
1983
|
expect(params.iops).toBe("5000");
|
|
1978
1984
|
expect(params.throughput).toBe("250");
|
|
1979
1985
|
});
|
|
1980
1986
|
|
|
1981
1987
|
test("string iops and throughput passed through", () => {
|
|
1982
1988
|
const result = EbsStorageClass({ name: "perf", iops: "3000", throughput: "125" });
|
|
1983
|
-
const params = (result.storageClass as any).parameters;
|
|
1989
|
+
const params = (p(result.storageClass) as any).parameters;
|
|
1984
1990
|
expect(params.iops).toBe("3000");
|
|
1985
1991
|
expect(params.throughput).toBe("125");
|
|
1986
1992
|
});
|
|
@@ -1991,17 +1997,17 @@ describe("EbsStorageClass", () => {
|
|
|
1991
1997
|
describe("EfsStorageClass", () => {
|
|
1992
1998
|
test("returns storageClass with EFS provisioner", () => {
|
|
1993
1999
|
const result = EfsStorageClass({ name: "efs", fileSystemId: "fs-123" });
|
|
1994
|
-
expect((result.storageClass as any).provisioner).toBe("efs.csi.aws.com");
|
|
2000
|
+
expect((p(result.storageClass) as any).provisioner).toBe("efs.csi.aws.com");
|
|
1995
2001
|
});
|
|
1996
2002
|
|
|
1997
2003
|
test("fileSystemId in parameters", () => {
|
|
1998
2004
|
const result = EfsStorageClass({ name: "efs", fileSystemId: "fs-abc" });
|
|
1999
|
-
expect((result.storageClass as any).parameters.fileSystemId).toBe("fs-abc");
|
|
2005
|
+
expect((p(result.storageClass) as any).parameters.fileSystemId).toBe("fs-abc");
|
|
2000
2006
|
});
|
|
2001
2007
|
|
|
2002
2008
|
test("default provisioningMode is efs-ap", () => {
|
|
2003
2009
|
const result = EfsStorageClass({ name: "efs", fileSystemId: "fs-123" });
|
|
2004
|
-
expect((result.storageClass as any).parameters.provisioningMode).toBe("efs-ap");
|
|
2010
|
+
expect((p(result.storageClass) as any).parameters.provisioningMode).toBe("efs-ap");
|
|
2005
2011
|
});
|
|
2006
2012
|
});
|
|
2007
2013
|
|
|
@@ -2021,36 +2027,36 @@ describe("FluentBitAgent", () => {
|
|
|
2021
2027
|
|
|
2022
2028
|
test("default namespace is amazon-cloudwatch", () => {
|
|
2023
2029
|
const result = FluentBitAgent(minProps);
|
|
2024
|
-
expect((result.daemonSet.metadata as any).namespace).toBe("amazon-cloudwatch");
|
|
2030
|
+
expect((p(result.daemonSet).metadata as any).namespace).toBe("amazon-cloudwatch");
|
|
2025
2031
|
});
|
|
2026
2032
|
|
|
2027
2033
|
test("configMap contains fluent-bit config with region", () => {
|
|
2028
2034
|
const result = FluentBitAgent(minProps);
|
|
2029
|
-
const data = (result.configMap as any).data;
|
|
2035
|
+
const data = (p(result.configMap) as any).data;
|
|
2030
2036
|
expect(data["fluent-bit.conf"]).toContain("us-east-1");
|
|
2031
2037
|
expect(data["fluent-bit.conf"]).toContain("/aws/eks/cluster/containers");
|
|
2032
2038
|
});
|
|
2033
2039
|
|
|
2034
2040
|
test("tolerations for all nodes", () => {
|
|
2035
2041
|
const result = FluentBitAgent(minProps);
|
|
2036
|
-
const spec = result.daemonSet.spec as any;
|
|
2042
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2037
2043
|
expect(spec.template.spec.tolerations).toEqual([{ operator: "Exists" }]);
|
|
2038
2044
|
});
|
|
2039
2045
|
|
|
2040
2046
|
test("clusterRole is cluster-scoped", () => {
|
|
2041
2047
|
const result = FluentBitAgent(minProps);
|
|
2042
|
-
expect((result.clusterRole.metadata as any).namespace).toBeUndefined();
|
|
2048
|
+
expect((p(result.clusterRole).metadata as any).namespace).toBeUndefined();
|
|
2043
2049
|
});
|
|
2044
2050
|
|
|
2045
2051
|
test("IRSA annotation when iamRoleArn set", () => {
|
|
2046
2052
|
const result = FluentBitAgent({ ...minProps, iamRoleArn: "arn:aws:iam::123456789012:role/fb-role" });
|
|
2047
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2053
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2048
2054
|
expect(meta.annotations["eks.amazonaws.com/role-arn"]).toBe("arn:aws:iam::123456789012:role/fb-role");
|
|
2049
2055
|
});
|
|
2050
2056
|
|
|
2051
2057
|
test("no annotation when iamRoleArn omitted", () => {
|
|
2052
2058
|
const result = FluentBitAgent(minProps);
|
|
2053
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2059
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2054
2060
|
expect(meta.annotations).toBeUndefined();
|
|
2055
2061
|
});
|
|
2056
2062
|
});
|
|
@@ -2073,32 +2079,32 @@ describe("ExternalDnsAgent", () => {
|
|
|
2073
2079
|
|
|
2074
2080
|
test("IRSA annotation on serviceAccount", () => {
|
|
2075
2081
|
const result = ExternalDnsAgent(minProps);
|
|
2076
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2082
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2077
2083
|
expect(meta.annotations["eks.amazonaws.com/role-arn"]).toBe("arn:aws:iam::123456789012:role/external-dns");
|
|
2078
2084
|
});
|
|
2079
2085
|
|
|
2080
2086
|
test("domain filter in args", () => {
|
|
2081
2087
|
const result = ExternalDnsAgent(minProps);
|
|
2082
|
-
const spec = result.deployment.spec as any;
|
|
2088
|
+
const spec = p(result.deployment).spec as any;
|
|
2083
2089
|
const args = spec.template.spec.containers[0].args;
|
|
2084
2090
|
expect(args).toContain("--domain-filter=example.com");
|
|
2085
2091
|
});
|
|
2086
2092
|
|
|
2087
2093
|
test("txtOwnerId in args when set", () => {
|
|
2088
2094
|
const result = ExternalDnsAgent({ ...minProps, txtOwnerId: "my-cluster" });
|
|
2089
|
-
const spec = result.deployment.spec as any;
|
|
2095
|
+
const spec = p(result.deployment).spec as any;
|
|
2090
2096
|
const args = spec.template.spec.containers[0].args;
|
|
2091
2097
|
expect(args).toContain("--txt-owner-id=my-cluster");
|
|
2092
2098
|
});
|
|
2093
2099
|
|
|
2094
2100
|
test("default namespace is kube-system", () => {
|
|
2095
2101
|
const result = ExternalDnsAgent(minProps);
|
|
2096
|
-
expect((result.deployment.metadata as any).namespace).toBe("kube-system");
|
|
2102
|
+
expect((p(result.deployment).metadata as any).namespace).toBe("kube-system");
|
|
2097
2103
|
});
|
|
2098
2104
|
|
|
2099
2105
|
test("replicas is 1", () => {
|
|
2100
2106
|
const result = ExternalDnsAgent(minProps);
|
|
2101
|
-
const spec = result.deployment.spec as any;
|
|
2107
|
+
const spec = p(result.deployment).spec as any;
|
|
2102
2108
|
expect(spec.replicas).toBe(1);
|
|
2103
2109
|
});
|
|
2104
2110
|
});
|
|
@@ -2119,19 +2125,19 @@ describe("AdotCollector", () => {
|
|
|
2119
2125
|
|
|
2120
2126
|
test("default namespace is amazon-metrics", () => {
|
|
2121
2127
|
const result = AdotCollector(minProps);
|
|
2122
|
-
expect((result.daemonSet.metadata as any).namespace).toBe("amazon-metrics");
|
|
2128
|
+
expect((p(result.daemonSet).metadata as any).namespace).toBe("amazon-metrics");
|
|
2123
2129
|
});
|
|
2124
2130
|
|
|
2125
2131
|
test("configMap contains ADOT config with region", () => {
|
|
2126
2132
|
const result = AdotCollector(minProps);
|
|
2127
|
-
const data = (result.configMap as any).data;
|
|
2133
|
+
const data = (p(result.configMap) as any).data;
|
|
2128
2134
|
expect(data["config.yaml"]).toContain("us-east-1");
|
|
2129
2135
|
expect(data["config.yaml"]).toContain("cluster");
|
|
2130
2136
|
});
|
|
2131
2137
|
|
|
2132
2138
|
test("OTLP ports on container", () => {
|
|
2133
2139
|
const result = AdotCollector(minProps);
|
|
2134
|
-
const spec = result.daemonSet.spec as any;
|
|
2140
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2135
2141
|
const ports = spec.template.spec.containers[0].ports;
|
|
2136
2142
|
expect(ports).toHaveLength(2);
|
|
2137
2143
|
expect(ports[0].containerPort).toBe(4317);
|
|
@@ -2140,25 +2146,25 @@ describe("AdotCollector", () => {
|
|
|
2140
2146
|
|
|
2141
2147
|
test("tolerations for all nodes", () => {
|
|
2142
2148
|
const result = AdotCollector(minProps);
|
|
2143
|
-
const spec = result.daemonSet.spec as any;
|
|
2149
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2144
2150
|
expect(spec.template.spec.tolerations).toEqual([{ operator: "Exists" }]);
|
|
2145
2151
|
});
|
|
2146
2152
|
|
|
2147
2153
|
test("custom exporters", () => {
|
|
2148
2154
|
const result = AdotCollector({ ...minProps, exporters: ["prometheus"] });
|
|
2149
|
-
const data = (result.configMap as any).data;
|
|
2155
|
+
const data = (p(result.configMap) as any).data;
|
|
2150
2156
|
expect(data["config.yaml"]).toContain("prometheusremotewrite");
|
|
2151
2157
|
});
|
|
2152
2158
|
|
|
2153
2159
|
test("IRSA annotation when iamRoleArn set", () => {
|
|
2154
2160
|
const result = AdotCollector({ ...minProps, iamRoleArn: "arn:aws:iam::123456789012:role/adot-role" });
|
|
2155
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2161
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2156
2162
|
expect(meta.annotations["eks.amazonaws.com/role-arn"]).toBe("arn:aws:iam::123456789012:role/adot-role");
|
|
2157
2163
|
});
|
|
2158
2164
|
|
|
2159
2165
|
test("no annotation when iamRoleArn omitted", () => {
|
|
2160
2166
|
const result = AdotCollector(minProps);
|
|
2161
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2167
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2162
2168
|
expect(meta.annotations).toBeUndefined();
|
|
2163
2169
|
});
|
|
2164
2170
|
});
|
|
@@ -2180,21 +2186,21 @@ describe("MetricsServer", () => {
|
|
|
2180
2186
|
|
|
2181
2187
|
test("default namespace is kube-system", () => {
|
|
2182
2188
|
const result = MetricsServer({});
|
|
2183
|
-
expect((result.deployment.metadata as any).namespace).toBe("kube-system");
|
|
2184
|
-
expect((result.service.metadata as any).namespace).toBe("kube-system");
|
|
2185
|
-
expect((result.serviceAccount.metadata as any).namespace).toBe("kube-system");
|
|
2189
|
+
expect((p(result.deployment).metadata as any).namespace).toBe("kube-system");
|
|
2190
|
+
expect((p(result.service).metadata as any).namespace).toBe("kube-system");
|
|
2191
|
+
expect((p(result.serviceAccount).metadata as any).namespace).toBe("kube-system");
|
|
2186
2192
|
});
|
|
2187
2193
|
|
|
2188
2194
|
test("service targets port 10250", () => {
|
|
2189
2195
|
const result = MetricsServer({});
|
|
2190
|
-
const spec = result.service.spec as any;
|
|
2196
|
+
const spec = p(result.service).spec as any;
|
|
2191
2197
|
expect(spec.ports[0].port).toBe(443);
|
|
2192
2198
|
expect(spec.ports[0].targetPort).toBe(10250);
|
|
2193
2199
|
});
|
|
2194
2200
|
|
|
2195
2201
|
test("deployment container has correct image and args", () => {
|
|
2196
2202
|
const result = MetricsServer({});
|
|
2197
|
-
const spec = result.deployment.spec as any;
|
|
2203
|
+
const spec = p(result.deployment).spec as any;
|
|
2198
2204
|
const container = spec.template.spec.containers[0];
|
|
2199
2205
|
expect(container.image).toBe("registry.k8s.io/metrics-server/metrics-server:v0.7.2");
|
|
2200
2206
|
expect(container.args).toContain("--secure-port=10250");
|
|
@@ -2204,14 +2210,14 @@ describe("MetricsServer", () => {
|
|
|
2204
2210
|
|
|
2205
2211
|
test("clusterRole has nodes/metrics access", () => {
|
|
2206
2212
|
const result = MetricsServer({});
|
|
2207
|
-
const rules = result.clusterRole.rules as any[];
|
|
2213
|
+
const rules = p(result.clusterRole).rules as any[];
|
|
2208
2214
|
const nodeMetricsRule = rules.find((r: any) => r.resources?.includes("nodes/metrics"));
|
|
2209
2215
|
expect(nodeMetricsRule).toBeDefined();
|
|
2210
2216
|
});
|
|
2211
2217
|
|
|
2212
2218
|
test("apiService references correct service", () => {
|
|
2213
2219
|
const result = MetricsServer({});
|
|
2214
|
-
const spec = (result.apiService as any).spec;
|
|
2220
|
+
const spec = (p(result.apiService) as any).spec;
|
|
2215
2221
|
expect(spec.service.name).toBe("metrics-server");
|
|
2216
2222
|
expect(spec.service.namespace).toBe("kube-system");
|
|
2217
2223
|
expect(spec.group).toBe("metrics.k8s.io");
|
|
@@ -2220,14 +2226,14 @@ describe("MetricsServer", () => {
|
|
|
2220
2226
|
|
|
2221
2227
|
test("aggregated clusterRole has aggregate labels", () => {
|
|
2222
2228
|
const result = MetricsServer({});
|
|
2223
|
-
const labels = (result.aggregatedClusterRole.metadata as any).labels;
|
|
2229
|
+
const labels = (p(result.aggregatedClusterRole).metadata as any).labels;
|
|
2224
2230
|
expect(labels["rbac.authorization.k8s.io/aggregate-to-admin"]).toBe("true");
|
|
2225
2231
|
expect(labels["rbac.authorization.k8s.io/aggregate-to-view"]).toBe("true");
|
|
2226
2232
|
});
|
|
2227
2233
|
|
|
2228
2234
|
test("custom image and replicas", () => {
|
|
2229
2235
|
const result = MetricsServer({ image: "custom:v1", replicas: 2 });
|
|
2230
|
-
const spec = result.deployment.spec as any;
|
|
2236
|
+
const spec = p(result.deployment).spec as any;
|
|
2231
2237
|
expect(spec.replicas).toBe(2);
|
|
2232
2238
|
expect(spec.template.spec.containers[0].image).toBe("custom:v1");
|
|
2233
2239
|
});
|
|
@@ -2250,7 +2256,7 @@ describe("BatchJob securityContext", () => {
|
|
|
2250
2256
|
image: "migrate:1.0",
|
|
2251
2257
|
securityContext: pssSecurityContext,
|
|
2252
2258
|
});
|
|
2253
|
-
const spec = result.job.spec as any;
|
|
2259
|
+
const spec = p(result.job).spec as any;
|
|
2254
2260
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2255
2261
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2256
2262
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2267,7 +2273,7 @@ describe("CronWorkload securityContext", () => {
|
|
|
2267
2273
|
schedule: "0 2 * * *",
|
|
2268
2274
|
securityContext: pssSecurityContext,
|
|
2269
2275
|
});
|
|
2270
|
-
const spec = result.cronJob.spec as any;
|
|
2276
|
+
const spec = p(result.cronJob).spec as any;
|
|
2271
2277
|
const sc = spec.jobTemplate.spec.template.spec.containers[0].securityContext;
|
|
2272
2278
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2273
2279
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2284,7 +2290,7 @@ describe("NodeAgent securityContext", () => {
|
|
|
2284
2290
|
rbacRules: [{ apiGroups: [""], resources: ["pods"], verbs: ["get"] }],
|
|
2285
2291
|
securityContext: pssSecurityContext,
|
|
2286
2292
|
});
|
|
2287
|
-
const spec = result.daemonSet.spec as any;
|
|
2293
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2288
2294
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2289
2295
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2290
2296
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2299,7 +2305,7 @@ describe("ConfiguredApp securityContext", () => {
|
|
|
2299
2305
|
image: "api:1.0",
|
|
2300
2306
|
securityContext: pssSecurityContext,
|
|
2301
2307
|
});
|
|
2302
|
-
const spec = result.deployment.spec as any;
|
|
2308
|
+
const spec = p(result.deployment).spec as any;
|
|
2303
2309
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2304
2310
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2305
2311
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2316,7 +2322,7 @@ describe("SidecarApp securityContext", () => {
|
|
|
2316
2322
|
sidecars: [{ name: "envoy", image: "envoy:1.0" }],
|
|
2317
2323
|
securityContext: pssSecurityContext,
|
|
2318
2324
|
});
|
|
2319
|
-
const spec = result.deployment.spec as any;
|
|
2325
|
+
const spec = p(result.deployment).spec as any;
|
|
2320
2326
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2321
2327
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2322
2328
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2332,7 +2338,7 @@ describe("NetworkIsolatedApp securityContext", () => {
|
|
|
2332
2338
|
image: "api:1.0",
|
|
2333
2339
|
securityContext: pssSecurityContext,
|
|
2334
2340
|
});
|
|
2335
|
-
const spec = result.deployment.spec as any;
|
|
2341
|
+
const spec = p(result.deployment).spec as any;
|
|
2336
2342
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2337
2343
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2338
2344
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2348,7 +2354,7 @@ describe("MonitoredService securityContext", () => {
|
|
|
2348
2354
|
image: "api:1.0",
|
|
2349
2355
|
securityContext: pssSecurityContext,
|
|
2350
2356
|
});
|
|
2351
|
-
const spec = result.deployment.spec as any;
|
|
2357
|
+
const spec = p(result.deployment).spec as any;
|
|
2352
2358
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2353
2359
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2354
2360
|
expect(sc.allowPrivilegeEscalation).toBe(false);
|
|
@@ -2365,7 +2371,7 @@ describe("ExternalDnsAgent security defaults", () => {
|
|
|
2365
2371
|
iamRoleArn: "arn:aws:iam::123456789012:role/test",
|
|
2366
2372
|
domainFilters: ["example.com"],
|
|
2367
2373
|
});
|
|
2368
|
-
const spec = result.deployment.spec as any;
|
|
2374
|
+
const spec = p(result.deployment).spec as any;
|
|
2369
2375
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2370
2376
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2371
2377
|
expect(sc.readOnlyRootFilesystem).toBe(true);
|
|
@@ -2380,7 +2386,7 @@ describe("FluentBitAgent security defaults", () => {
|
|
|
2380
2386
|
region: "us-east-1",
|
|
2381
2387
|
clusterName: "test",
|
|
2382
2388
|
});
|
|
2383
|
-
const spec = result.daemonSet.spec as any;
|
|
2389
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2384
2390
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2385
2391
|
expect(sc.runAsUser).toBe(0);
|
|
2386
2392
|
expect(sc.runAsNonRoot).toBeUndefined();
|
|
@@ -2395,7 +2401,7 @@ describe("AdotCollector security defaults", () => {
|
|
|
2395
2401
|
region: "us-east-1",
|
|
2396
2402
|
clusterName: "test",
|
|
2397
2403
|
});
|
|
2398
|
-
const spec = result.daemonSet.spec as any;
|
|
2404
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2399
2405
|
const sc = spec.template.spec.containers[0].securityContext;
|
|
2400
2406
|
expect(sc.runAsNonRoot).toBe(true);
|
|
2401
2407
|
expect(sc.runAsUser).toBe(10001);
|
|
@@ -2407,9 +2413,10 @@ describe("AdotCollector security defaults", () => {
|
|
|
2407
2413
|
// ── Phase 3B: Composite serialization smoke tests ───────────────
|
|
2408
2414
|
|
|
2409
2415
|
describe("Composite YAML serialization smoke tests", () => {
|
|
2410
|
-
function serializeCompositeProps(
|
|
2411
|
-
return Object.values(
|
|
2412
|
-
.
|
|
2416
|
+
function serializeCompositeProps(composite: Record<string, unknown>): string {
|
|
2417
|
+
return Object.values(composite)
|
|
2418
|
+
.filter((v) => v != null && typeof v === "object" && p(v as any) != null)
|
|
2419
|
+
.map((v) => emitYAML(p(v as any), 0))
|
|
2413
2420
|
.join("\n---\n");
|
|
2414
2421
|
}
|
|
2415
2422
|
|
|
@@ -2462,7 +2469,7 @@ describe("Composite YAML serialization smoke tests", () => {
|
|
|
2462
2469
|
describe("MetricsServer RBAC completeness", () => {
|
|
2463
2470
|
test("clusterRole includes configmaps resource", () => {
|
|
2464
2471
|
const result = MetricsServer({});
|
|
2465
|
-
const rules = result.clusterRole.rules as any[];
|
|
2472
|
+
const rules = p(result.clusterRole).rules as any[];
|
|
2466
2473
|
const hasConfigmaps = rules.some((r: any) => r.resources?.includes("configmaps"));
|
|
2467
2474
|
expect(hasConfigmaps).toBe(true);
|
|
2468
2475
|
});
|
|
@@ -2476,7 +2483,7 @@ describe("AdotCollector command vs args", () => {
|
|
|
2476
2483
|
region: "us-east-1",
|
|
2477
2484
|
clusterName: "test",
|
|
2478
2485
|
});
|
|
2479
|
-
const spec = result.daemonSet.spec as any;
|
|
2486
|
+
const spec = p(result.daemonSet).spec as any;
|
|
2480
2487
|
const container = spec.template.spec.containers[0];
|
|
2481
2488
|
// Config flag should be in args, not command
|
|
2482
2489
|
expect(container.args).toContain("--config=/etc/adot/config.yaml");
|
|
@@ -2493,7 +2500,7 @@ describe("AdotCollector pipeline exporter separation", () => {
|
|
|
2493
2500
|
clusterName: "test",
|
|
2494
2501
|
exporters: ["cloudwatch", "xray"],
|
|
2495
2502
|
});
|
|
2496
|
-
const config = (result.configMap as any).data["config.yaml"] as string;
|
|
2503
|
+
const config = (p(result.configMap) as any).data["config.yaml"] as string;
|
|
2497
2504
|
// Extract metrics pipeline exporters line
|
|
2498
2505
|
const metricsMatch = config.match(/metrics:\s*\n\s*receivers:.*\n\s*processors:.*\n\s*exporters:\s*\[([^\]]+)\]/);
|
|
2499
2506
|
expect(metricsMatch).toBeDefined();
|
|
@@ -2508,7 +2515,7 @@ describe("AdotCollector pipeline exporter separation", () => {
|
|
|
2508
2515
|
clusterName: "test",
|
|
2509
2516
|
exporters: ["cloudwatch", "xray"],
|
|
2510
2517
|
});
|
|
2511
|
-
const config = (result.configMap as any).data["config.yaml"] as string;
|
|
2518
|
+
const config = (p(result.configMap) as any).data["config.yaml"] as string;
|
|
2512
2519
|
// Extract traces pipeline exporters line
|
|
2513
2520
|
const tracesMatch = config.match(/traces:\s*\n\s*receivers:.*\n\s*processors:.*\n\s*exporters:\s*\[([^\]]+)\]/);
|
|
2514
2521
|
expect(tracesMatch).toBeDefined();
|
|
@@ -2523,7 +2530,7 @@ describe("AdotCollector pipeline exporter separation", () => {
|
|
|
2523
2530
|
clusterName: "test",
|
|
2524
2531
|
exporters: ["cloudwatch"],
|
|
2525
2532
|
});
|
|
2526
|
-
const config = (result.configMap as any).data["config.yaml"] as string;
|
|
2533
|
+
const config = (p(result.configMap) as any).data["config.yaml"] as string;
|
|
2527
2534
|
// Traces pipeline should still have an exporter (fallback to awsxray)
|
|
2528
2535
|
const tracesMatch = config.match(/traces:\s*\n\s*receivers:.*\n\s*processors:.*\n\s*exporters:\s*\[([^\]]+)\]/);
|
|
2529
2536
|
expect(tracesMatch).toBeDefined();
|
|
@@ -2540,7 +2547,7 @@ describe("AdotCollector config structure", () => {
|
|
|
2540
2547
|
region: "us-east-1",
|
|
2541
2548
|
clusterName: "test",
|
|
2542
2549
|
});
|
|
2543
|
-
const config = (result.configMap as any).data["config.yaml"] as string;
|
|
2550
|
+
const config = (p(result.configMap) as any).data["config.yaml"] as string;
|
|
2544
2551
|
expect(config).toContain("receivers:");
|
|
2545
2552
|
expect(config).toContain("exporters:");
|
|
2546
2553
|
expect(config).toContain("processors:");
|
|
@@ -2559,7 +2566,7 @@ describe("WorkloadIdentityServiceAccount", () => {
|
|
|
2559
2566
|
test("returns serviceAccount with Workload Identity annotation", () => {
|
|
2560
2567
|
const result = WorkloadIdentityServiceAccount(minProps);
|
|
2561
2568
|
expect(result.serviceAccount).toBeDefined();
|
|
2562
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2569
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2563
2570
|
expect(meta.annotations["iam.gke.io/gcp-service-account"]).toBe("sa@my-project.iam.gserviceaccount.com");
|
|
2564
2571
|
});
|
|
2565
2572
|
|
|
@@ -2576,18 +2583,18 @@ describe("WorkloadIdentityServiceAccount", () => {
|
|
|
2576
2583
|
});
|
|
2577
2584
|
expect(result.role).toBeDefined();
|
|
2578
2585
|
expect(result.roleBinding).toBeDefined();
|
|
2579
|
-
const role = result.role as any;
|
|
2586
|
+
const role = p(result.role) as any;
|
|
2580
2587
|
expect(role.rules[0].resources).toEqual(["secrets"]);
|
|
2581
2588
|
});
|
|
2582
2589
|
|
|
2583
2590
|
test("namespace propagated", () => {
|
|
2584
2591
|
const result = WorkloadIdentityServiceAccount({ ...minProps, namespace: "prod" });
|
|
2585
|
-
expect((result.serviceAccount.metadata as any).namespace).toBe("prod");
|
|
2592
|
+
expect((p(result.serviceAccount).metadata as any).namespace).toBe("prod");
|
|
2586
2593
|
});
|
|
2587
2594
|
|
|
2588
2595
|
test("component labels", () => {
|
|
2589
2596
|
const result = WorkloadIdentityServiceAccount(minProps);
|
|
2590
|
-
expect((result.serviceAccount.metadata as any).labels["app.kubernetes.io/component"]).toBe("service-account");
|
|
2597
|
+
expect((p(result.serviceAccount).metadata as any).labels["app.kubernetes.io/component"]).toBe("service-account");
|
|
2591
2598
|
});
|
|
2592
2599
|
});
|
|
2593
2600
|
|
|
@@ -2597,37 +2604,37 @@ describe("GcePdStorageClass", () => {
|
|
|
2597
2604
|
test("returns storageClass with GCE PD provisioner", () => {
|
|
2598
2605
|
const result = GcePdStorageClass({ name: "pd-balanced" });
|
|
2599
2606
|
expect(result.storageClass).toBeDefined();
|
|
2600
|
-
expect((result.storageClass as any).provisioner).toBe("pd.csi.storage.gke.io");
|
|
2607
|
+
expect((p(result.storageClass) as any).provisioner).toBe("pd.csi.storage.gke.io");
|
|
2601
2608
|
});
|
|
2602
2609
|
|
|
2603
2610
|
test("default type is pd-balanced", () => {
|
|
2604
2611
|
const result = GcePdStorageClass({ name: "default" });
|
|
2605
|
-
expect((result.storageClass as any).parameters.type).toBe("pd-balanced");
|
|
2612
|
+
expect((p(result.storageClass) as any).parameters.type).toBe("pd-balanced");
|
|
2606
2613
|
});
|
|
2607
2614
|
|
|
2608
2615
|
test("custom type", () => {
|
|
2609
2616
|
const result = GcePdStorageClass({ name: "ssd", type: "pd-ssd" });
|
|
2610
|
-
expect((result.storageClass as any).parameters.type).toBe("pd-ssd");
|
|
2617
|
+
expect((p(result.storageClass) as any).parameters.type).toBe("pd-ssd");
|
|
2611
2618
|
});
|
|
2612
2619
|
|
|
2613
2620
|
test("regional-pd replication type", () => {
|
|
2614
2621
|
const result = GcePdStorageClass({ name: "regional", replicationType: "regional-pd" });
|
|
2615
|
-
expect((result.storageClass as any).parameters["replication-type"]).toBe("regional-pd");
|
|
2622
|
+
expect((p(result.storageClass) as any).parameters["replication-type"]).toBe("regional-pd");
|
|
2616
2623
|
});
|
|
2617
2624
|
|
|
2618
2625
|
test("no replication-type param when none", () => {
|
|
2619
2626
|
const result = GcePdStorageClass({ name: "default" });
|
|
2620
|
-
expect((result.storageClass as any).parameters["replication-type"]).toBeUndefined();
|
|
2627
|
+
expect((p(result.storageClass) as any).parameters["replication-type"]).toBeUndefined();
|
|
2621
2628
|
});
|
|
2622
2629
|
|
|
2623
2630
|
test("allowVolumeExpansion default true", () => {
|
|
2624
2631
|
const result = GcePdStorageClass({ name: "exp" });
|
|
2625
|
-
expect((result.storageClass as any).allowVolumeExpansion).toBe(true);
|
|
2632
|
+
expect((p(result.storageClass) as any).allowVolumeExpansion).toBe(true);
|
|
2626
2633
|
});
|
|
2627
2634
|
|
|
2628
2635
|
test("storageClass is cluster-scoped (no namespace)", () => {
|
|
2629
2636
|
const result = GcePdStorageClass({ name: "sc" });
|
|
2630
|
-
expect((result.storageClass.metadata as any).namespace).toBeUndefined();
|
|
2637
|
+
expect((p(result.storageClass).metadata as any).namespace).toBeUndefined();
|
|
2631
2638
|
});
|
|
2632
2639
|
});
|
|
2633
2640
|
|
|
@@ -2636,27 +2643,27 @@ describe("GcePdStorageClass", () => {
|
|
|
2636
2643
|
describe("FilestoreStorageClass", () => {
|
|
2637
2644
|
test("returns storageClass with Filestore provisioner", () => {
|
|
2638
2645
|
const result = FilestoreStorageClass({ name: "filestore" });
|
|
2639
|
-
expect((result.storageClass as any).provisioner).toBe("filestore.csi.storage.gke.io");
|
|
2646
|
+
expect((p(result.storageClass) as any).provisioner).toBe("filestore.csi.storage.gke.io");
|
|
2640
2647
|
});
|
|
2641
2648
|
|
|
2642
2649
|
test("default tier is standard", () => {
|
|
2643
2650
|
const result = FilestoreStorageClass({ name: "fs" });
|
|
2644
|
-
expect((result.storageClass as any).parameters.tier).toBe("standard");
|
|
2651
|
+
expect((p(result.storageClass) as any).parameters.tier).toBe("standard");
|
|
2645
2652
|
});
|
|
2646
2653
|
|
|
2647
2654
|
test("custom tier", () => {
|
|
2648
2655
|
const result = FilestoreStorageClass({ name: "premium-fs", tier: "premium" });
|
|
2649
|
-
expect((result.storageClass as any).parameters.tier).toBe("premium");
|
|
2656
|
+
expect((p(result.storageClass) as any).parameters.tier).toBe("premium");
|
|
2650
2657
|
});
|
|
2651
2658
|
|
|
2652
2659
|
test("network parameter set when provided", () => {
|
|
2653
2660
|
const result = FilestoreStorageClass({ name: "fs", network: "my-vpc" });
|
|
2654
|
-
expect((result.storageClass as any).parameters.network).toBe("my-vpc");
|
|
2661
|
+
expect((p(result.storageClass) as any).parameters.network).toBe("my-vpc");
|
|
2655
2662
|
});
|
|
2656
2663
|
|
|
2657
2664
|
test("no network parameter by default", () => {
|
|
2658
2665
|
const result = FilestoreStorageClass({ name: "fs" });
|
|
2659
|
-
expect((result.storageClass as any).parameters.network).toBeUndefined();
|
|
2666
|
+
expect((p(result.storageClass) as any).parameters.network).toBeUndefined();
|
|
2660
2667
|
});
|
|
2661
2668
|
});
|
|
2662
2669
|
|
|
@@ -2676,26 +2683,26 @@ describe("GkeGateway", () => {
|
|
|
2676
2683
|
|
|
2677
2684
|
test("default gatewayClassName", () => {
|
|
2678
2685
|
const result = GkeGateway(minProps);
|
|
2679
|
-
const spec = result.gateway.spec as any;
|
|
2686
|
+
const spec = p(result.gateway).spec as any;
|
|
2680
2687
|
expect(spec.gatewayClassName).toBe("gke-l7-global-external-managed");
|
|
2681
2688
|
});
|
|
2682
2689
|
|
|
2683
2690
|
test("custom gatewayClassName", () => {
|
|
2684
2691
|
const result = GkeGateway({ ...minProps, gatewayClassName: "gke-l7-rilb" });
|
|
2685
|
-
const spec = result.gateway.spec as any;
|
|
2692
|
+
const spec = p(result.gateway).spec as any;
|
|
2686
2693
|
expect(spec.gatewayClassName).toBe("gke-l7-rilb");
|
|
2687
2694
|
});
|
|
2688
2695
|
|
|
2689
2696
|
test("HTTP listener when no certificate", () => {
|
|
2690
2697
|
const result = GkeGateway(minProps);
|
|
2691
|
-
const spec = result.gateway.spec as any;
|
|
2698
|
+
const spec = p(result.gateway).spec as any;
|
|
2692
2699
|
expect(spec.listeners[0].protocol).toBe("HTTP");
|
|
2693
2700
|
expect(spec.listeners[0].port).toBe(80);
|
|
2694
2701
|
});
|
|
2695
2702
|
|
|
2696
2703
|
test("HTTPS listener with certificate", () => {
|
|
2697
2704
|
const result = GkeGateway({ ...minProps, certificateName: "api-cert" });
|
|
2698
|
-
const spec = result.gateway.spec as any;
|
|
2705
|
+
const spec = p(result.gateway).spec as any;
|
|
2699
2706
|
expect(spec.listeners[0].protocol).toBe("HTTPS");
|
|
2700
2707
|
expect(spec.listeners[0].port).toBe(443);
|
|
2701
2708
|
expect(spec.listeners[0].tls.certificateRefs[0].name).toBe("api-cert");
|
|
@@ -2703,27 +2710,27 @@ describe("GkeGateway", () => {
|
|
|
2703
2710
|
|
|
2704
2711
|
test("httpRoute references parent gateway", () => {
|
|
2705
2712
|
const result = GkeGateway(minProps);
|
|
2706
|
-
const spec = result.httpRoute.spec as any;
|
|
2713
|
+
const spec = p(result.httpRoute).spec as any;
|
|
2707
2714
|
expect(spec.parentRefs[0].name).toBe("api-gateway");
|
|
2708
2715
|
});
|
|
2709
2716
|
|
|
2710
2717
|
test("httpRoute has hostnames", () => {
|
|
2711
2718
|
const result = GkeGateway(minProps);
|
|
2712
|
-
const spec = result.httpRoute.spec as any;
|
|
2719
|
+
const spec = p(result.httpRoute).spec as any;
|
|
2713
2720
|
expect(spec.hostnames).toEqual(["api.example.com"]);
|
|
2714
2721
|
});
|
|
2715
2722
|
|
|
2716
2723
|
test("httpRoute rules map to backend services", () => {
|
|
2717
2724
|
const result = GkeGateway(minProps);
|
|
2718
|
-
const spec = result.httpRoute.spec as any;
|
|
2725
|
+
const spec = p(result.httpRoute).spec as any;
|
|
2719
2726
|
expect(spec.rules[0].backendRefs[0].name).toBe("api");
|
|
2720
2727
|
expect(spec.rules[0].backendRefs[0].port).toBe(80);
|
|
2721
2728
|
});
|
|
2722
2729
|
|
|
2723
2730
|
test("namespace propagated to both resources", () => {
|
|
2724
2731
|
const result = GkeGateway({ ...minProps, namespace: "prod" });
|
|
2725
|
-
expect((result.gateway.metadata as any).namespace).toBe("prod");
|
|
2726
|
-
expect((result.httpRoute.metadata as any).namespace).toBe("prod");
|
|
2732
|
+
expect((p(result.gateway).metadata as any).namespace).toBe("prod");
|
|
2733
|
+
expect((p(result.httpRoute).metadata as any).namespace).toBe("prod");
|
|
2727
2734
|
});
|
|
2728
2735
|
});
|
|
2729
2736
|
|
|
@@ -2735,34 +2742,34 @@ describe("ConfigConnectorContext", () => {
|
|
|
2735
2742
|
test("returns context with apiVersion and kind", () => {
|
|
2736
2743
|
const result = ConfigConnectorContext(minProps);
|
|
2737
2744
|
expect(result.context).toBeDefined();
|
|
2738
|
-
expect((result.context as any).apiVersion).toBe("core.cnrm.cloud.google.com/v1beta1");
|
|
2739
|
-
expect((result.context as any).kind).toBe("ConfigConnectorContext");
|
|
2745
|
+
expect((p(result.context) as any).apiVersion).toBe("core.cnrm.cloud.google.com/v1beta1");
|
|
2746
|
+
expect((p(result.context) as any).kind).toBe("ConfigConnectorContext");
|
|
2740
2747
|
});
|
|
2741
2748
|
|
|
2742
2749
|
test("googleServiceAccount in spec", () => {
|
|
2743
2750
|
const result = ConfigConnectorContext(minProps);
|
|
2744
|
-
const spec = (result.context as any).spec;
|
|
2751
|
+
const spec = (p(result.context) as any).spec;
|
|
2745
2752
|
expect(spec.googleServiceAccount).toBe("cnrm@my-project.iam.gserviceaccount.com");
|
|
2746
2753
|
});
|
|
2747
2754
|
|
|
2748
|
-
test("default stateIntoSpec is
|
|
2755
|
+
test("default stateIntoSpec is Absent", () => {
|
|
2749
2756
|
const result = ConfigConnectorContext(minProps);
|
|
2750
|
-
expect((result.context as any).spec.stateIntoSpec).toBe("
|
|
2757
|
+
expect((p(result.context) as any).spec.stateIntoSpec).toBe("Absent");
|
|
2751
2758
|
});
|
|
2752
2759
|
|
|
2753
2760
|
test("custom stateIntoSpec", () => {
|
|
2754
|
-
const result = ConfigConnectorContext({ ...minProps, stateIntoSpec: "
|
|
2755
|
-
expect((result.context as any).spec.stateIntoSpec).toBe("
|
|
2761
|
+
const result = ConfigConnectorContext({ ...minProps, stateIntoSpec: "Merge" });
|
|
2762
|
+
expect((p(result.context) as any).spec.stateIntoSpec).toBe("Merge");
|
|
2756
2763
|
});
|
|
2757
2764
|
|
|
2758
2765
|
test("default namespace is default", () => {
|
|
2759
2766
|
const result = ConfigConnectorContext(minProps);
|
|
2760
|
-
expect((result.context as any).metadata.namespace).toBe("default");
|
|
2767
|
+
expect((p(result.context) as any).metadata.namespace).toBe("default");
|
|
2761
2768
|
});
|
|
2762
2769
|
|
|
2763
2770
|
test("custom namespace", () => {
|
|
2764
2771
|
const result = ConfigConnectorContext({ ...minProps, namespace: "config-connector" });
|
|
2765
|
-
expect((result.context as any).metadata.namespace).toBe("config-connector");
|
|
2772
|
+
expect((p(result.context) as any).metadata.namespace).toBe("config-connector");
|
|
2766
2773
|
});
|
|
2767
2774
|
});
|
|
2768
2775
|
|
|
@@ -2788,7 +2795,7 @@ describe("GkeFluentBitAgent", () => {
|
|
|
2788
2795
|
|
|
2789
2796
|
test("SA has GKE Workload Identity annotation", () => {
|
|
2790
2797
|
const result = GkeFluentBitAgent(minProps);
|
|
2791
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2798
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2792
2799
|
expect(meta.annotations["iam.gke.io/gcp-service-account"]).toBe(
|
|
2793
2800
|
"fluent-bit@test-project.iam.gserviceaccount.com",
|
|
2794
2801
|
);
|
|
@@ -2799,40 +2806,40 @@ describe("GkeFluentBitAgent", () => {
|
|
|
2799
2806
|
clusterName: "test-cluster",
|
|
2800
2807
|
projectId: "test-project",
|
|
2801
2808
|
});
|
|
2802
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2809
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2803
2810
|
expect(meta.annotations).toBeUndefined();
|
|
2804
2811
|
});
|
|
2805
2812
|
|
|
2806
2813
|
test("configMap uses stackdriver output", () => {
|
|
2807
2814
|
const result = GkeFluentBitAgent(minProps);
|
|
2808
|
-
const data = result.configMap.data as any;
|
|
2815
|
+
const data = p(result.configMap).data as any;
|
|
2809
2816
|
expect(data["fluent-bit.conf"]).toContain("stackdriver");
|
|
2810
2817
|
expect(data["fluent-bit.conf"]).toContain("test-cluster");
|
|
2811
2818
|
});
|
|
2812
2819
|
|
|
2813
2820
|
test("default namespace is gke-logging", () => {
|
|
2814
2821
|
const result = GkeFluentBitAgent(minProps);
|
|
2815
|
-
expect((result.daemonSet.metadata as any).namespace).toBe("gke-logging");
|
|
2816
|
-
expect((result.serviceAccount.metadata as any).namespace).toBe("gke-logging");
|
|
2822
|
+
expect((p(result.daemonSet).metadata as any).namespace).toBe("gke-logging");
|
|
2823
|
+
expect((p(result.serviceAccount).metadata as any).namespace).toBe("gke-logging");
|
|
2817
2824
|
});
|
|
2818
2825
|
|
|
2819
2826
|
test("common labels on all resources", () => {
|
|
2820
2827
|
const result = GkeFluentBitAgent(minProps);
|
|
2821
2828
|
for (const resource of [result.daemonSet, result.serviceAccount, result.clusterRole, result.clusterRoleBinding, result.configMap]) {
|
|
2822
|
-
expect((resource.metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
2829
|
+
expect((p(resource).metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
2823
2830
|
}
|
|
2824
2831
|
});
|
|
2825
2832
|
|
|
2826
2833
|
test("DaemonSet mounts host log directory", () => {
|
|
2827
2834
|
const result = GkeFluentBitAgent(minProps);
|
|
2828
|
-
const spec = (result.daemonSet as any).spec.template.spec;
|
|
2835
|
+
const spec = (p(result.daemonSet) as any).spec.template.spec;
|
|
2829
2836
|
const varlog = spec.volumes.find((v: any) => v.name === "varlog");
|
|
2830
2837
|
expect(varlog.hostPath.path).toBe("/var/log");
|
|
2831
2838
|
});
|
|
2832
2839
|
|
|
2833
2840
|
test("container runs as root for log access", () => {
|
|
2834
2841
|
const result = GkeFluentBitAgent(minProps);
|
|
2835
|
-
const container = (result.daemonSet as any).spec.template.spec.containers[0];
|
|
2842
|
+
const container = (p(result.daemonSet) as any).spec.template.spec.containers[0];
|
|
2836
2843
|
expect(container.securityContext.runAsUser).toBe(0);
|
|
2837
2844
|
expect(container.securityContext.readOnlyRootFilesystem).toBe(true);
|
|
2838
2845
|
});
|
|
@@ -2860,7 +2867,7 @@ describe("GkeOtelCollector", () => {
|
|
|
2860
2867
|
|
|
2861
2868
|
test("SA has GKE Workload Identity annotation", () => {
|
|
2862
2869
|
const result = GkeOtelCollector(minProps);
|
|
2863
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2870
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2864
2871
|
expect(meta.annotations["iam.gke.io/gcp-service-account"]).toBe(
|
|
2865
2872
|
"otel@test-project.iam.gserviceaccount.com",
|
|
2866
2873
|
);
|
|
@@ -2871,25 +2878,25 @@ describe("GkeOtelCollector", () => {
|
|
|
2871
2878
|
clusterName: "test-cluster",
|
|
2872
2879
|
projectId: "test-project",
|
|
2873
2880
|
});
|
|
2874
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2881
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2875
2882
|
expect(meta.annotations).toBeUndefined();
|
|
2876
2883
|
});
|
|
2877
2884
|
|
|
2878
2885
|
test("configMap uses googlecloud exporter", () => {
|
|
2879
2886
|
const result = GkeOtelCollector(minProps);
|
|
2880
|
-
const data = result.configMap.data as any;
|
|
2887
|
+
const data = p(result.configMap).data as any;
|
|
2881
2888
|
expect(data["config.yaml"]).toContain("googlecloud");
|
|
2882
2889
|
expect(data["config.yaml"]).toContain("test-project");
|
|
2883
2890
|
});
|
|
2884
2891
|
|
|
2885
2892
|
test("default namespace is gke-monitoring", () => {
|
|
2886
2893
|
const result = GkeOtelCollector(minProps);
|
|
2887
|
-
expect((result.daemonSet.metadata as any).namespace).toBe("gke-monitoring");
|
|
2894
|
+
expect((p(result.daemonSet).metadata as any).namespace).toBe("gke-monitoring");
|
|
2888
2895
|
});
|
|
2889
2896
|
|
|
2890
2897
|
test("container exposes OTLP ports", () => {
|
|
2891
2898
|
const result = GkeOtelCollector(minProps);
|
|
2892
|
-
const container = (result.daemonSet as any).spec.template.spec.containers[0];
|
|
2899
|
+
const container = (p(result.daemonSet) as any).spec.template.spec.containers[0];
|
|
2893
2900
|
const ports = container.ports.map((p: any) => p.containerPort);
|
|
2894
2901
|
expect(ports).toContain(4317);
|
|
2895
2902
|
expect(ports).toContain(4318);
|
|
@@ -2897,7 +2904,7 @@ describe("GkeOtelCollector", () => {
|
|
|
2897
2904
|
|
|
2898
2905
|
test("container runs as non-root", () => {
|
|
2899
2906
|
const result = GkeOtelCollector(minProps);
|
|
2900
|
-
const container = (result.daemonSet as any).spec.template.spec.containers[0];
|
|
2907
|
+
const container = (p(result.daemonSet) as any).spec.template.spec.containers[0];
|
|
2901
2908
|
expect(container.securityContext.runAsNonRoot).toBe(true);
|
|
2902
2909
|
expect(container.securityContext.runAsUser).toBe(10001);
|
|
2903
2910
|
});
|
|
@@ -2905,7 +2912,7 @@ describe("GkeOtelCollector", () => {
|
|
|
2905
2912
|
test("common labels on all resources", () => {
|
|
2906
2913
|
const result = GkeOtelCollector(minProps);
|
|
2907
2914
|
for (const resource of [result.daemonSet, result.serviceAccount, result.clusterRole, result.clusterRoleBinding, result.configMap]) {
|
|
2908
|
-
expect((resource.metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
2915
|
+
expect((p(resource).metadata as any).labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
2909
2916
|
}
|
|
2910
2917
|
});
|
|
2911
2918
|
});
|
|
@@ -2931,7 +2938,7 @@ describe("GkeExternalDnsAgent", () => {
|
|
|
2931
2938
|
|
|
2932
2939
|
test("SA has GKE Workload Identity annotation", () => {
|
|
2933
2940
|
const result = GkeExternalDnsAgent(minProps);
|
|
2934
|
-
const meta = result.serviceAccount.metadata as any;
|
|
2941
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
2935
2942
|
expect(meta.annotations["iam.gke.io/gcp-service-account"]).toBe(
|
|
2936
2943
|
"external-dns@test-project.iam.gserviceaccount.com",
|
|
2937
2944
|
);
|
|
@@ -2939,36 +2946,36 @@ describe("GkeExternalDnsAgent", () => {
|
|
|
2939
2946
|
|
|
2940
2947
|
test("uses google provider", () => {
|
|
2941
2948
|
const result = GkeExternalDnsAgent(minProps);
|
|
2942
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
2949
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
2943
2950
|
expect(container.args).toContain("--provider=google");
|
|
2944
2951
|
});
|
|
2945
2952
|
|
|
2946
2953
|
test("passes google-project arg", () => {
|
|
2947
2954
|
const result = GkeExternalDnsAgent(minProps);
|
|
2948
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
2955
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
2949
2956
|
expect(container.args).toContain("--google-project=test-project");
|
|
2950
2957
|
});
|
|
2951
2958
|
|
|
2952
2959
|
test("domain filters are applied", () => {
|
|
2953
2960
|
const result = GkeExternalDnsAgent(minProps);
|
|
2954
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
2961
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
2955
2962
|
expect(container.args).toContain("--domain-filter=example.com");
|
|
2956
2963
|
});
|
|
2957
2964
|
|
|
2958
2965
|
test("txtOwnerId is applied when set", () => {
|
|
2959
2966
|
const result = GkeExternalDnsAgent({ ...minProps, txtOwnerId: "my-cluster" });
|
|
2960
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
2967
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
2961
2968
|
expect(container.args).toContain("--txt-owner-id=my-cluster");
|
|
2962
2969
|
});
|
|
2963
2970
|
|
|
2964
2971
|
test("default namespace is kube-system", () => {
|
|
2965
2972
|
const result = GkeExternalDnsAgent(minProps);
|
|
2966
|
-
expect((result.deployment.metadata as any).namespace).toBe("kube-system");
|
|
2973
|
+
expect((p(result.deployment).metadata as any).namespace).toBe("kube-system");
|
|
2967
2974
|
});
|
|
2968
2975
|
|
|
2969
2976
|
test("container runs as non-root", () => {
|
|
2970
2977
|
const result = GkeExternalDnsAgent(minProps);
|
|
2971
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
2978
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
2972
2979
|
expect(container.securityContext.runAsNonRoot).toBe(true);
|
|
2973
2980
|
expect(container.securityContext.runAsUser).toBe(65534);
|
|
2974
2981
|
});
|
|
@@ -2997,7 +3004,7 @@ describe("AksExternalDnsAgent", () => {
|
|
|
2997
3004
|
|
|
2998
3005
|
test("SA has AKS Workload Identity annotation and label", () => {
|
|
2999
3006
|
const result = AksExternalDnsAgent(minProps);
|
|
3000
|
-
const meta = result.serviceAccount.metadata as any;
|
|
3007
|
+
const meta = p(result.serviceAccount).metadata as any;
|
|
3001
3008
|
expect(meta.annotations["azure.workload.identity/client-id"]).toBe(
|
|
3002
3009
|
"00000000-0000-0000-0000-000000000000",
|
|
3003
3010
|
);
|
|
@@ -3006,20 +3013,20 @@ describe("AksExternalDnsAgent", () => {
|
|
|
3006
3013
|
|
|
3007
3014
|
test("uses azure provider", () => {
|
|
3008
3015
|
const result = AksExternalDnsAgent(minProps);
|
|
3009
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
3016
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
3010
3017
|
expect(container.args).toContain("--provider=azure");
|
|
3011
3018
|
});
|
|
3012
3019
|
|
|
3013
3020
|
test("passes azure resource group and subscription", () => {
|
|
3014
3021
|
const result = AksExternalDnsAgent(minProps);
|
|
3015
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
3022
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
3016
3023
|
expect(container.args).toContain("--azure-resource-group=test-rg");
|
|
3017
3024
|
expect(container.args).toContain("--azure-subscription-id=11111111-1111-1111-1111-111111111111");
|
|
3018
3025
|
});
|
|
3019
3026
|
|
|
3020
3027
|
test("container has Azure env vars", () => {
|
|
3021
3028
|
const result = AksExternalDnsAgent(minProps);
|
|
3022
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
3029
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
3023
3030
|
const envMap = Object.fromEntries(container.env.map((e: any) => [e.name, e.value]));
|
|
3024
3031
|
expect(envMap.AZURE_TENANT_ID).toBe("22222222-2222-2222-2222-222222222222");
|
|
3025
3032
|
expect(envMap.AZURE_SUBSCRIPTION_ID).toBe("11111111-1111-1111-1111-111111111111");
|
|
@@ -3028,24 +3035,24 @@ describe("AksExternalDnsAgent", () => {
|
|
|
3028
3035
|
|
|
3029
3036
|
test("pod labels include workload identity use label", () => {
|
|
3030
3037
|
const result = AksExternalDnsAgent(minProps);
|
|
3031
|
-
const podLabels = (result.deployment as any).spec.template.metadata.labels;
|
|
3038
|
+
const podLabels = (p(result.deployment) as any).spec.template.metadata.labels;
|
|
3032
3039
|
expect(podLabels["azure.workload.identity/use"]).toBe("true");
|
|
3033
3040
|
});
|
|
3034
3041
|
|
|
3035
3042
|
test("domain filters are applied", () => {
|
|
3036
3043
|
const result = AksExternalDnsAgent(minProps);
|
|
3037
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
3044
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
3038
3045
|
expect(container.args).toContain("--domain-filter=example.com");
|
|
3039
3046
|
});
|
|
3040
3047
|
|
|
3041
3048
|
test("default namespace is kube-system", () => {
|
|
3042
3049
|
const result = AksExternalDnsAgent(minProps);
|
|
3043
|
-
expect((result.deployment.metadata as any).namespace).toBe("kube-system");
|
|
3050
|
+
expect((p(result.deployment).metadata as any).namespace).toBe("kube-system");
|
|
3044
3051
|
});
|
|
3045
3052
|
|
|
3046
3053
|
test("container runs as non-root", () => {
|
|
3047
3054
|
const result = AksExternalDnsAgent(minProps);
|
|
3048
|
-
const container = (result.deployment as any).spec.template.spec.containers[0];
|
|
3055
|
+
const container = (p(result.deployment) as any).spec.template.spec.containers[0];
|
|
3049
3056
|
expect(container.securityContext.runAsNonRoot).toBe(true);
|
|
3050
3057
|
expect(container.securityContext.runAsUser).toBe(65534);
|
|
3051
3058
|
});
|
|
@@ -3075,19 +3082,19 @@ describe("CockroachDbCluster", () => {
|
|
|
3075
3082
|
|
|
3076
3083
|
test("default replicas is 3", () => {
|
|
3077
3084
|
const result = CockroachDbCluster(minProps);
|
|
3078
|
-
const spec = result.statefulSet.spec as any;
|
|
3085
|
+
const spec = p(result.statefulSet).spec as any;
|
|
3079
3086
|
expect(spec.replicas).toBe(3);
|
|
3080
3087
|
});
|
|
3081
3088
|
|
|
3082
3089
|
test("default image is cockroachdb/cockroach:v24.3.0", () => {
|
|
3083
3090
|
const result = CockroachDbCluster(minProps);
|
|
3084
|
-
const container = (result.statefulSet.spec as any).template.spec.containers[0];
|
|
3091
|
+
const container = (p(result.statefulSet).spec as any).template.spec.containers[0];
|
|
3085
3092
|
expect(container.image).toBe("cockroachdb/cockroach:v24.3.0");
|
|
3086
3093
|
});
|
|
3087
3094
|
|
|
3088
3095
|
test("StatefulSet has correct ports (26257+8080)", () => {
|
|
3089
3096
|
const result = CockroachDbCluster(minProps);
|
|
3090
|
-
const container = (result.statefulSet.spec as any).template.spec.containers[0];
|
|
3097
|
+
const container = (p(result.statefulSet).spec as any).template.spec.containers[0];
|
|
3091
3098
|
const ports = container.ports.map((p: any) => p.containerPort);
|
|
3092
3099
|
expect(ports).toContain(26257);
|
|
3093
3100
|
expect(ports).toContain(8080);
|
|
@@ -3095,36 +3102,36 @@ describe("CockroachDbCluster", () => {
|
|
|
3095
3102
|
|
|
3096
3103
|
test("StatefulSet has PVC with default 100Gi storage", () => {
|
|
3097
3104
|
const result = CockroachDbCluster(minProps);
|
|
3098
|
-
const vct = (result.statefulSet.spec as any).volumeClaimTemplates[0];
|
|
3105
|
+
const vct = (p(result.statefulSet).spec as any).volumeClaimTemplates[0];
|
|
3099
3106
|
expect(vct.spec.resources.requests.storage).toBe("100Gi");
|
|
3100
3107
|
expect(vct.spec.accessModes).toEqual(["ReadWriteOnce"]);
|
|
3101
3108
|
});
|
|
3102
3109
|
|
|
3103
3110
|
test("headless service has clusterIP None and publishNotReadyAddresses", () => {
|
|
3104
3111
|
const result = CockroachDbCluster(minProps);
|
|
3105
|
-
const spec = result.headlessService.spec as any;
|
|
3112
|
+
const spec = p(result.headlessService).spec as any;
|
|
3106
3113
|
expect(spec.clusterIP).toBe("None");
|
|
3107
3114
|
expect(spec.publishNotReadyAddresses).toBe(true);
|
|
3108
3115
|
});
|
|
3109
3116
|
|
|
3110
3117
|
test("public service has ClusterIP type with both ports", () => {
|
|
3111
3118
|
const result = CockroachDbCluster(minProps);
|
|
3112
|
-
const spec = result.publicService.spec as any;
|
|
3119
|
+
const spec = p(result.publicService).spec as any;
|
|
3113
3120
|
expect(spec.type).toBe("ClusterIP");
|
|
3114
|
-
const ports = spec.ports.map((
|
|
3121
|
+
const ports = spec.ports.map((pt: any) => pt.port);
|
|
3115
3122
|
expect(ports).toContain(26257);
|
|
3116
3123
|
expect(ports).toContain(8080);
|
|
3117
3124
|
});
|
|
3118
3125
|
|
|
3119
3126
|
test("PDB has maxUnavailable 1", () => {
|
|
3120
3127
|
const result = CockroachDbCluster(minProps);
|
|
3121
|
-
const spec = result.pdb.spec as any;
|
|
3128
|
+
const spec = p(result.pdb).spec as any;
|
|
3122
3129
|
expect(spec.maxUnavailable).toBe(1);
|
|
3123
3130
|
});
|
|
3124
3131
|
|
|
3125
3132
|
test("StatefulSet has pod anti-affinity", () => {
|
|
3126
3133
|
const result = CockroachDbCluster(minProps);
|
|
3127
|
-
const affinity = (result.statefulSet.spec as any).template.spec.affinity;
|
|
3134
|
+
const affinity = (p(result.statefulSet).spec as any).template.spec.affinity;
|
|
3128
3135
|
expect(affinity.podAntiAffinity).toBeDefined();
|
|
3129
3136
|
});
|
|
3130
3137
|
|
|
@@ -3135,7 +3142,7 @@ describe("CockroachDbCluster", () => {
|
|
|
3135
3142
|
image: "cockroachdb/cockroach:v23.2.0",
|
|
3136
3143
|
storageSize: "200Gi",
|
|
3137
3144
|
});
|
|
3138
|
-
const spec = result.statefulSet.spec as any;
|
|
3145
|
+
const spec = p(result.statefulSet).spec as any;
|
|
3139
3146
|
expect(spec.replicas).toBe(5);
|
|
3140
3147
|
expect(spec.template.spec.containers[0].image).toBe("cockroachdb/cockroach:v23.2.0");
|
|
3141
3148
|
expect(spec.volumeClaimTemplates[0].spec.resources.requests.storage).toBe("200Gi");
|
|
@@ -3144,42 +3151,41 @@ describe("CockroachDbCluster", () => {
|
|
|
3144
3151
|
test("joinAddresses appear in container args", () => {
|
|
3145
3152
|
const joins = ["crdb-0.crdb.ns.svc.cluster.local", "crdb-1.crdb.ns.svc.cluster.local"];
|
|
3146
3153
|
const result = CockroachDbCluster({ name: "crdb", joinAddresses: joins });
|
|
3147
|
-
const
|
|
3148
|
-
|
|
3149
|
-
expect(
|
|
3150
|
-
expect(
|
|
3151
|
-
expect(joinArg).toContain("crdb-1.crdb.ns.svc.cluster.local");
|
|
3154
|
+
const cmd = (p(result.statefulSet).spec as any).template.spec.containers[0].args[0] as string;
|
|
3155
|
+
expect(cmd).toContain("--join=");
|
|
3156
|
+
expect(cmd).toContain("crdb-0.crdb.ns.svc.cluster.local");
|
|
3157
|
+
expect(cmd).toContain("crdb-1.crdb.ns.svc.cluster.local");
|
|
3152
3158
|
});
|
|
3153
3159
|
|
|
3154
3160
|
test("locality appears in container args when set", () => {
|
|
3155
3161
|
const result = CockroachDbCluster({ name: "crdb", locality: "cloud=aws,region=us-east-1" });
|
|
3156
|
-
const
|
|
3157
|
-
expect(
|
|
3162
|
+
const cmd = (p(result.statefulSet).spec as any).template.spec.containers[0].args[0] as string;
|
|
3163
|
+
expect(cmd).toContain("--locality=cloud=aws,region=us-east-1");
|
|
3158
3164
|
});
|
|
3159
3165
|
|
|
3160
3166
|
test("namespace is set on all namespaced resources", () => {
|
|
3161
3167
|
const result = CockroachDbCluster({ name: "crdb", namespace: "crdb-eks" });
|
|
3162
3168
|
for (const key of ["serviceAccount", "role", "roleBinding", "publicService", "headlessService", "pdb", "statefulSet", "initJob", "certGenJob"] as const) {
|
|
3163
|
-
expect((result[key].metadata as any).namespace).toBe("crdb-eks");
|
|
3169
|
+
expect((p(result[key]).metadata as any).namespace).toBe("crdb-eks");
|
|
3164
3170
|
}
|
|
3165
3171
|
});
|
|
3166
3172
|
|
|
3167
3173
|
test("cluster-scoped resources do not have namespace", () => {
|
|
3168
3174
|
const result = CockroachDbCluster({ name: "crdb", namespace: "crdb-eks" });
|
|
3169
|
-
expect((result.clusterRole.metadata as any).namespace).toBeUndefined();
|
|
3170
|
-
expect((result.clusterRoleBinding.metadata as any).namespace).toBeUndefined();
|
|
3175
|
+
expect((p(result.clusterRole).metadata as any).namespace).toBeUndefined();
|
|
3176
|
+
expect((p(result.clusterRoleBinding).metadata as any).namespace).toBeUndefined();
|
|
3171
3177
|
});
|
|
3172
3178
|
|
|
3173
3179
|
test("includes common labels", () => {
|
|
3174
3180
|
const result = CockroachDbCluster(minProps);
|
|
3175
|
-
const meta = result.statefulSet.metadata as any;
|
|
3181
|
+
const meta = p(result.statefulSet).metadata as any;
|
|
3176
3182
|
expect(meta.labels["app.kubernetes.io/name"]).toBe("cockroachdb");
|
|
3177
3183
|
expect(meta.labels["app.kubernetes.io/managed-by"]).toBe("chant");
|
|
3178
3184
|
});
|
|
3179
3185
|
|
|
3180
3186
|
test("secure mode mounts certs volume", () => {
|
|
3181
3187
|
const result = CockroachDbCluster({ name: "crdb", secure: true });
|
|
3182
|
-
const spec = (result.statefulSet.spec as any).template.spec;
|
|
3188
|
+
const spec = (p(result.statefulSet).spec as any).template.spec;
|
|
3183
3189
|
expect(spec.volumes).toBeDefined();
|
|
3184
3190
|
const certsVol = spec.volumes.find((v: any) => v.name === "certs");
|
|
3185
3191
|
expect(certsVol).toBeDefined();
|
|
@@ -3188,32 +3194,32 @@ describe("CockroachDbCluster", () => {
|
|
|
3188
3194
|
|
|
3189
3195
|
test("insecure mode omits certs volume", () => {
|
|
3190
3196
|
const result = CockroachDbCluster({ name: "crdb", secure: false });
|
|
3191
|
-
const spec = (result.statefulSet.spec as any).template.spec;
|
|
3197
|
+
const spec = (p(result.statefulSet).spec as any).template.spec;
|
|
3192
3198
|
expect(spec.volumes).toBeUndefined();
|
|
3193
|
-
const
|
|
3194
|
-
expect(
|
|
3199
|
+
const cmd = spec.containers[0].args[0] as string;
|
|
3200
|
+
expect(cmd).toContain("--insecure");
|
|
3195
3201
|
});
|
|
3196
3202
|
|
|
3197
3203
|
test("storageClassName is set when provided", () => {
|
|
3198
3204
|
const result = CockroachDbCluster({ name: "crdb", storageClassName: "gp3-encrypted" });
|
|
3199
|
-
const vct = (result.statefulSet.spec as any).volumeClaimTemplates[0];
|
|
3205
|
+
const vct = (p(result.statefulSet).spec as any).volumeClaimTemplates[0];
|
|
3200
3206
|
expect(vct.spec.storageClassName).toBe("gp3-encrypted");
|
|
3201
3207
|
});
|
|
3202
3208
|
|
|
3203
3209
|
test("init job references correct host", () => {
|
|
3204
3210
|
const result = CockroachDbCluster({ name: "crdb" });
|
|
3205
|
-
const container = (result.initJob.spec as any).template.spec.containers[0];
|
|
3211
|
+
const container = (p(result.initJob).spec as any).template.spec.containers[0];
|
|
3206
3212
|
expect(container.args).toContain("--host=crdb-0.crdb");
|
|
3207
3213
|
});
|
|
3208
3214
|
|
|
3209
3215
|
test("StatefulSet uses Parallel podManagementPolicy", () => {
|
|
3210
3216
|
const result = CockroachDbCluster(minProps);
|
|
3211
|
-
expect((result.statefulSet.spec as any).podManagementPolicy).toBe("Parallel");
|
|
3217
|
+
expect((p(result.statefulSet).spec as any).podManagementPolicy).toBe("Parallel");
|
|
3212
3218
|
});
|
|
3213
3219
|
|
|
3214
3220
|
test("cert-gen job uses same image as StatefulSet", () => {
|
|
3215
3221
|
const result = CockroachDbCluster({ name: "crdb", image: "cockroachdb/cockroach:v23.2.0" });
|
|
3216
|
-
const container = (result.certGenJob.spec as any).template.spec.containers[0];
|
|
3222
|
+
const container = (p(result.certGenJob).spec as any).template.spec.containers[0];
|
|
3217
3223
|
expect(container.image).toBe("cockroachdb/cockroach:v23.2.0");
|
|
3218
3224
|
});
|
|
3219
3225
|
});
|