@intentius/chant-lexicon-k8s 0.0.18 → 0.0.24

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.
Files changed (54) hide show
  1. package/dist/integrity.json +9 -4
  2. package/dist/manifest.json +1 -1
  3. package/dist/skills/chant-k8s-aks.md +146 -0
  4. package/{src/skills/kubernetes-patterns.md → dist/skills/chant-k8s-deployment-strategies.md} +1 -1
  5. package/dist/skills/chant-k8s-eks.md +156 -0
  6. package/dist/skills/chant-k8s-gke.md +246 -0
  7. package/{src/skills/kubernetes-security.md → dist/skills/chant-k8s-security.md} +1 -1
  8. package/dist/skills/chant-k8s.md +66 -3
  9. package/package.json +20 -2
  10. package/src/composites/adot-collector.ts +34 -22
  11. package/src/composites/agic-ingress.ts +14 -6
  12. package/src/composites/aks-external-dns-agent.ts +29 -18
  13. package/src/composites/alb-ingress.ts +14 -6
  14. package/src/composites/autoscaled-service.ts +25 -20
  15. package/src/composites/azure-disk-storage-class.ts +14 -6
  16. package/src/composites/azure-file-storage-class.ts +14 -6
  17. package/src/composites/azure-monitor-collector.ts +34 -22
  18. package/src/composites/batch-job.ts +25 -17
  19. package/src/composites/cockroachdb-cluster.ts +148 -58
  20. package/src/composites/composites.test.ts +369 -363
  21. package/src/composites/config-connector-context.ts +15 -8
  22. package/src/composites/configured-app.ts +21 -15
  23. package/src/composites/cron-workload.ts +25 -20
  24. package/src/composites/ebs-storage-class.ts +14 -6
  25. package/src/composites/efs-storage-class.ts +14 -6
  26. package/src/composites/external-dns-agent.ts +26 -20
  27. package/src/composites/filestore-storage-class.ts +14 -6
  28. package/src/composites/fluent-bit-agent.ts +30 -24
  29. package/src/composites/gce-ingress.ts +14 -6
  30. package/src/composites/gce-pd-storage-class.ts +14 -6
  31. package/src/composites/gke-external-dns-agent.ts +34 -21
  32. package/src/composites/gke-fluent-bit-agent.ts +34 -22
  33. package/src/composites/gke-gateway.ts +19 -12
  34. package/src/composites/gke-otel-collector.ts +34 -22
  35. package/src/composites/irsa-service-account.ts +22 -14
  36. package/src/composites/metrics-server.ts +41 -26
  37. package/src/composites/monitored-service.ts +26 -19
  38. package/src/composites/namespace-env.ts +26 -17
  39. package/src/composites/network-isolated-app.ts +21 -16
  40. package/src/composites/node-agent.ts +33 -22
  41. package/src/composites/secure-ingress.ts +19 -11
  42. package/src/composites/sidecar-app.ts +17 -12
  43. package/src/composites/stateful-app.ts +21 -12
  44. package/src/composites/web-app.ts +25 -21
  45. package/src/composites/worker-pool.ts +40 -26
  46. package/src/composites/workload-identity-sa.ts +22 -14
  47. package/src/composites/workload-identity-service-account.ts +22 -16
  48. package/src/plugin.ts +130 -614
  49. package/src/serializer.ts +3 -0
  50. package/src/skills/chant-k8s-deployment-strategies.md +183 -0
  51. package/src/skills/chant-k8s-gke.md +55 -0
  52. package/src/skills/chant-k8s-patterns.md +245 -0
  53. package/src/skills/chant-k8s-security.md +237 -0
  54. 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!.spec as any;
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!.spec as any;
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("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!.metadata as any;
634
- const roleMeta = result.role!.metadata as any;
635
- const bindingMeta = result.roleBinding!.metadata as any;
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("worker");
797
- expect((result.role!.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
798
- expect((result.roleBinding!.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
799
- expect((result.configMap!.metadata as any).labels["app.kubernetes.io/component"]).toBe("config");
800
- expect((result.hpa!.metadata as any).labels["app.kubernetes.io/component"]).toBe("autoscaler");
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!.spec as any;
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!.spec as any;
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!.spec as any;
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!.metadata as any;
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!.spec as any;
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!.metadata as any;
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!.spec as any;
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!.metadata as any;
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("quota");
967
- expect((result.limitRange!.metadata as any).labels["app.kubernetes.io/component"]).toBe("limits");
968
- expect((result.networkPolicy!.metadata as any).labels["app.kubernetes.io/component"]).toBe("network-policy");
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("config");
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!.spec as any;
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!.spec as any;
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!.spec as any;
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!.spec as any;
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!.metadata as any).namespace).toBe("jobs");
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("rbac");
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!.spec as any;
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!.spec as any;
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!.spec as any;
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!.metadata as any).labels["app.kubernetes.io/component"]).toBe("monitoring");
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(props: Record<string, Record<string, unknown>>): string {
2411
- return Object.values(props)
2412
- .map((p) => emitYAML(p, 0))
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
2755
  test("default stateIntoSpec is absent", () => {
2749
2756
  const result = ConfigConnectorContext(minProps);
2750
- expect((result.context as any).spec.stateIntoSpec).toBe("absent");
2757
+ expect((p(result.context) as any).spec.stateIntoSpec).toBe("absent");
2751
2758
  });
2752
2759
 
2753
2760
  test("custom stateIntoSpec", () => {
2754
2761
  const result = ConfigConnectorContext({ ...minProps, stateIntoSpec: "merge" });
2755
- expect((result.context as any).spec.stateIntoSpec).toBe("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((p: any) => p.port);
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 args = (result.statefulSet.spec as any).template.spec.containers[0].args as string[];
3148
- const joinArg = args.find((a: string) => a.startsWith("--join="));
3149
- expect(joinArg).toBeDefined();
3150
- expect(joinArg).toContain("crdb-0.crdb.ns.svc.cluster.local");
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 args = (result.statefulSet.spec as any).template.spec.containers[0].args as string[];
3157
- expect(args).toContain("--locality=cloud=aws,region=us-east-1");
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 args = spec.containers[0].args as string[];
3194
- expect(args).toContain("--insecure");
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
  });