timonel 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +5 -1
  3. package/SECURITY.md +15 -8
  4. package/dist/cli.js +29 -31
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +7 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +7 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/helm.d.ts +200 -12
  11. package/dist/lib/helm.d.ts.map +1 -1
  12. package/dist/lib/helm.js +234 -13
  13. package/dist/lib/helm.js.map +1 -1
  14. package/dist/lib/helmChartWriter.d.ts +238 -0
  15. package/dist/lib/helmChartWriter.d.ts.map +1 -0
  16. package/dist/lib/helmChartWriter.js +318 -0
  17. package/dist/lib/helmChartWriter.js.map +1 -0
  18. package/dist/lib/resources/autoscaling/autoscalingResources.d.ts +26 -0
  19. package/dist/lib/resources/autoscaling/autoscalingResources.d.ts.map +1 -0
  20. package/dist/lib/resources/autoscaling/autoscalingResources.js +35 -0
  21. package/dist/lib/resources/autoscaling/autoscalingResources.js.map +1 -0
  22. package/dist/lib/resources/baseResourceProvider.d.ts +47 -0
  23. package/dist/lib/resources/baseResourceProvider.d.ts.map +1 -0
  24. package/dist/lib/resources/baseResourceProvider.js +83 -0
  25. package/dist/lib/resources/baseResourceProvider.js.map +1 -0
  26. package/dist/lib/resources/cloud/aws/awsResources.d.ts +165 -0
  27. package/dist/lib/resources/cloud/aws/awsResources.d.ts.map +1 -0
  28. package/dist/lib/resources/cloud/aws/awsResources.js +187 -0
  29. package/dist/lib/resources/cloud/aws/awsResources.js.map +1 -0
  30. package/dist/lib/resources/cloud/azure/azureResources.d.ts +169 -0
  31. package/dist/lib/resources/cloud/azure/azureResources.d.ts.map +1 -0
  32. package/dist/lib/resources/cloud/azure/azureResources.js +218 -0
  33. package/dist/lib/resources/cloud/azure/azureResources.js.map +1 -0
  34. package/dist/lib/resources/cloud/gcp/gcpResources.d.ts +133 -0
  35. package/dist/lib/resources/cloud/gcp/gcpResources.d.ts.map +1 -0
  36. package/dist/lib/resources/cloud/gcp/gcpResources.js +162 -0
  37. package/dist/lib/resources/cloud/gcp/gcpResources.js.map +1 -0
  38. package/dist/lib/resources/core/coreResources.d.ts +584 -0
  39. package/dist/lib/resources/core/coreResources.d.ts.map +1 -0
  40. package/dist/lib/resources/core/coreResources.js +796 -0
  41. package/dist/lib/resources/core/coreResources.js.map +1 -0
  42. package/dist/lib/resources/core/storageResources.d.ts +133 -0
  43. package/dist/lib/resources/core/storageResources.d.ts.map +1 -0
  44. package/dist/lib/resources/core/storageResources.js +120 -0
  45. package/dist/lib/resources/core/storageResources.js.map +1 -0
  46. package/dist/lib/resources/validation/validationResources.d.ts +26 -0
  47. package/dist/lib/resources/validation/validationResources.d.ts.map +1 -0
  48. package/dist/lib/resources/validation/validationResources.js +33 -0
  49. package/dist/lib/resources/validation/validationResources.js.map +1 -0
  50. package/dist/lib/rutter.d.ts +554 -0
  51. package/dist/lib/rutter.d.ts.map +1 -0
  52. package/dist/lib/rutter.js +665 -0
  53. package/dist/lib/rutter.js.map +1 -0
  54. package/dist/lib/security.d.ts +73 -0
  55. package/dist/lib/security.d.ts.map +1 -0
  56. package/dist/lib/security.js +138 -0
  57. package/dist/lib/security.js.map +1 -0
  58. package/dist/lib/umbrella.d.ts +22 -2
  59. package/dist/lib/umbrella.d.ts.map +1 -1
  60. package/dist/lib/umbrella.js +21 -1
  61. package/dist/lib/umbrella.js.map +1 -1
  62. package/dist/lib/umbrellaRutter.d.ts +97 -0
  63. package/dist/lib/umbrellaRutter.d.ts.map +1 -0
  64. package/dist/lib/{UmbrellaRutter.js → umbrellaRutter.js} +72 -23
  65. package/dist/lib/umbrellaRutter.js.map +1 -0
  66. package/package.json +2 -1
  67. package/dist/lib/HelmChartWriter.d.ts +0 -69
  68. package/dist/lib/HelmChartWriter.d.ts.map +0 -1
  69. package/dist/lib/HelmChartWriter.js +0 -124
  70. package/dist/lib/HelmChartWriter.js.map +0 -1
  71. package/dist/lib/Rutter.d.ts +0 -1334
  72. package/dist/lib/Rutter.d.ts.map +0 -1
  73. package/dist/lib/Rutter.js +0 -2138
  74. package/dist/lib/Rutter.js.map +0 -1
  75. package/dist/lib/UmbrellaRutter.d.ts +0 -34
  76. package/dist/lib/UmbrellaRutter.d.ts.map +0 -1
  77. package/dist/lib/UmbrellaRutter.js.map +0 -1
@@ -1,2138 +0,0 @@
1
- import { App, Chart, Testing, ApiObject } from 'cdk8s';
2
- import YAML from 'yaml';
3
- import { include, helm } from './helm.js';
4
- import { HelmChartWriter } from './HelmChartWriter.js';
5
- /**
6
- * Rutter builds Kubernetes manifests using cdk8s and writes a Helm chart.
7
- */
8
- export class Rutter {
9
- constructor(props) {
10
- this.assets = [];
11
- this.valueOverrides = {};
12
- this.props = props;
13
- this.app = new App();
14
- this.chart = new Chart(this.app, props.manifestName ?? props.meta.name);
15
- }
16
- /**
17
- * Set dynamic value overrides (from --set flags)
18
- */
19
- setValues(overrides) {
20
- this.valueOverrides = { ...this.valueOverrides, ...overrides };
21
- }
22
- /**
23
- * Apply value overrides to nested object using dot notation
24
- */
25
- applyOverrides(values) {
26
- const result = JSON.parse(JSON.stringify(values)); // Deep clone
27
- for (const [key, value] of Object.entries(this.valueOverrides)) {
28
- this.setNestedValue(result, key, this.parseValue(value));
29
- }
30
- return result;
31
- }
32
- setNestedValue(obj, path, value) {
33
- /* eslint-disable security/detect-object-injection */
34
- const keys = path.split('.').filter(Boolean);
35
- if (keys.length === 0)
36
- return;
37
- let current = obj;
38
- for (let i = 0; i < keys.length - 1; i++) {
39
- const key = keys[i];
40
- if (!key)
41
- continue;
42
- if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
43
- current[key] = {};
44
- }
45
- current = current[key];
46
- }
47
- const finalKey = keys[keys.length - 1];
48
- if (finalKey) {
49
- current[finalKey] = value;
50
- }
51
- /* eslint-enable security/detect-object-injection */
52
- }
53
- parseValue(value) {
54
- // Try to parse as JSON first (for objects, arrays, booleans, numbers)
55
- try {
56
- return JSON.parse(value);
57
- }
58
- catch {
59
- // If not valid JSON, treat as string
60
- return value;
61
- }
62
- }
63
- addDeployment(spec) {
64
- const match = spec.matchLabels ?? {
65
- [Rutter.LABEL_NAME]: include(Rutter.HELPER_NAME),
66
- [Rutter.LABEL_INSTANCE]: helm.releaseName,
67
- };
68
- const envEntries = spec.env
69
- ? Object.entries(spec.env).map(([name, value]) => ({ name, value }))
70
- : undefined;
71
- const envFromEntries = spec.envFrom
72
- ? spec.envFrom.map((s) => ({
73
- configMapRef: s.configMapRef ? { name: s.configMapRef } : undefined,
74
- secretRef: s.secretRef ? { name: s.secretRef } : undefined,
75
- }))
76
- : undefined;
77
- const volumeDefs = spec.volumes
78
- ? spec.volumes.map((v) => ({
79
- name: v.name,
80
- configMap: v.configMap ? { name: v.configMap } : undefined,
81
- secret: v.secret ? { secretName: v.secret } : undefined,
82
- persistentVolumeClaim: v.persistentVolumeClaim
83
- ? { claimName: v.persistentVolumeClaim }
84
- : undefined,
85
- }))
86
- : undefined;
87
- const volumeMountDefs = spec.volumeMounts
88
- ? spec.volumeMounts.map((m) => ({
89
- name: m.name,
90
- mountPath: m.mountPath,
91
- readOnly: m.readOnly,
92
- subPath: m.subPath,
93
- }))
94
- : undefined;
95
- const templateLabels = { ...match, ...(spec.podLabels ?? {}) };
96
- const dep = new ApiObject(this.chart, spec.name, {
97
- apiVersion: 'apps/v1',
98
- kind: 'Deployment',
99
- metadata: {
100
- name: spec.name,
101
- ...(spec.labels ? { labels: spec.labels } : {}),
102
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
103
- },
104
- spec: {
105
- replicas: spec.replicas ?? 1,
106
- selector: { matchLabels: match },
107
- template: {
108
- metadata: {
109
- labels: templateLabels,
110
- ...(spec.podAnnotations ? { annotations: spec.podAnnotations } : {}),
111
- },
112
- spec: {
113
- serviceAccountName: spec.serviceAccountName,
114
- volumes: volumeDefs,
115
- containers: [
116
- {
117
- name: spec.name,
118
- image: spec.image,
119
- imagePullPolicy: spec.imagePullPolicy,
120
- ports: spec.containerPort ? [{ containerPort: spec.containerPort }] : undefined,
121
- env: envEntries,
122
- envFrom: envFromEntries,
123
- volumeMounts: volumeMountDefs,
124
- resources: spec.resources,
125
- livenessProbe: spec.livenessProbe,
126
- readinessProbe: spec.readinessProbe,
127
- },
128
- ],
129
- },
130
- },
131
- },
132
- });
133
- this.capture(dep, `${spec.name}-deployment`);
134
- return dep;
135
- }
136
- addService(spec) {
137
- const svc = new ApiObject(this.chart, spec.name, {
138
- apiVersion: 'v1',
139
- kind: 'Service',
140
- metadata: {
141
- name: spec.name,
142
- ...(spec.labels ? { labels: spec.labels } : {}),
143
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
144
- },
145
- spec: {
146
- type: spec.type ?? 'ClusterIP',
147
- selector: spec.selector,
148
- ports: spec.ports.map((p) => ({
149
- port: p.port,
150
- targetPort: p.targetPort ?? p.port,
151
- protocol: p.protocol ?? 'TCP',
152
- name: p.name,
153
- nodePort: p.nodePort,
154
- })),
155
- clusterIP: spec.clusterIP,
156
- externalName: spec.externalName,
157
- sessionAffinity: spec.sessionAffinity,
158
- loadBalancerIP: spec.loadBalancerIP,
159
- loadBalancerSourceRanges: spec.loadBalancerSourceRanges,
160
- loadBalancerClass: spec.loadBalancerClass,
161
- externalTrafficPolicy: spec.externalTrafficPolicy,
162
- internalTrafficPolicy: spec.internalTrafficPolicy,
163
- ipFamilyPolicy: spec.ipFamilyPolicy,
164
- ipFamilies: spec.ipFamilies,
165
- },
166
- });
167
- this.capture(svc, `${spec.name}-service`);
168
- return svc;
169
- }
170
- addReplicaSet(spec) {
171
- const match = spec.matchLabels ?? {
172
- [Rutter.LABEL_NAME]: include(Rutter.HELPER_NAME),
173
- [Rutter.LABEL_INSTANCE]: helm.releaseName,
174
- };
175
- const envEntries = spec.env
176
- ? Object.entries(spec.env).map(([name, value]) => ({ name, value }))
177
- : undefined;
178
- const envFromEntries = spec.envFrom
179
- ? spec.envFrom.map((s) => ({
180
- configMapRef: s.configMapRef ? { name: s.configMapRef } : undefined,
181
- secretRef: s.secretRef ? { name: s.secretRef } : undefined,
182
- }))
183
- : undefined;
184
- const volumeDefs = spec.volumes
185
- ? spec.volumes.map((v) => ({
186
- name: v.name,
187
- configMap: v.configMap ? { name: v.configMap } : undefined,
188
- secret: v.secret ? { secretName: v.secret } : undefined,
189
- persistentVolumeClaim: v.persistentVolumeClaim
190
- ? { claimName: v.persistentVolumeClaim }
191
- : undefined,
192
- }))
193
- : undefined;
194
- const volumeMountDefs = spec.volumeMounts
195
- ? spec.volumeMounts.map((m) => ({
196
- name: m.name,
197
- mountPath: m.mountPath,
198
- readOnly: m.readOnly,
199
- subPath: m.subPath,
200
- }))
201
- : undefined;
202
- const templateLabels = { ...match, ...(spec.podLabels ?? {}) };
203
- const rs = new ApiObject(this.chart, spec.name, {
204
- apiVersion: 'apps/v1',
205
- kind: 'ReplicaSet',
206
- metadata: {
207
- name: spec.name,
208
- ...(spec.labels ? { labels: spec.labels } : {}),
209
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
210
- },
211
- spec: {
212
- replicas: spec.replicas ?? 1,
213
- selector: { matchLabels: match },
214
- template: {
215
- metadata: {
216
- labels: templateLabels,
217
- ...(spec.podAnnotations ? { annotations: spec.podAnnotations } : {}),
218
- },
219
- spec: {
220
- serviceAccountName: spec.serviceAccountName,
221
- volumes: volumeDefs,
222
- containers: [
223
- {
224
- name: spec.name,
225
- image: spec.image,
226
- imagePullPolicy: spec.imagePullPolicy,
227
- ports: spec.containerPort ? [{ containerPort: spec.containerPort }] : undefined,
228
- env: envEntries,
229
- envFrom: envFromEntries,
230
- volumeMounts: volumeMountDefs,
231
- resources: spec.resources,
232
- livenessProbe: spec.livenessProbe,
233
- readinessProbe: spec.readinessProbe,
234
- },
235
- ],
236
- },
237
- },
238
- },
239
- });
240
- this.capture(rs, `${spec.name}-replicaset`);
241
- return rs;
242
- }
243
- addJob(spec) {
244
- // Validate Job specification
245
- this.validateJobSpec(spec);
246
- const { envEntries, envFromEntries, volumeDefs, volumeMountDefs } = this.buildPodEnvironment(spec);
247
- const jobSpec = this.buildJobSpec(spec);
248
- const podTemplate = this.buildJobPodTemplate(spec, envEntries, envFromEntries, volumeDefs, volumeMountDefs);
249
- const job = new ApiObject(this.chart, spec.name, {
250
- apiVersion: 'batch/v1',
251
- kind: 'Job',
252
- metadata: {
253
- name: spec.name,
254
- ...(spec.labels ? { labels: spec.labels } : {}),
255
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
256
- },
257
- spec: {
258
- ...jobSpec,
259
- template: podTemplate,
260
- },
261
- });
262
- this.capture(job, `${spec.name}-job`);
263
- return job;
264
- }
265
- addCronJob(spec) {
266
- // Validate CronJob specification
267
- this.validateCronJobSpec(spec);
268
- const { envEntries, envFromEntries, volumeDefs, volumeMountDefs } = this.buildPodEnvironment(spec);
269
- const cronJobSpec = this.buildCronJobSpec(spec);
270
- const jobTemplate = this.buildCronJobTemplate(spec, envEntries, envFromEntries, volumeDefs, volumeMountDefs);
271
- const cronJob = new ApiObject(this.chart, spec.name, {
272
- apiVersion: 'batch/v1',
273
- kind: 'CronJob',
274
- metadata: {
275
- name: spec.name,
276
- ...(spec.labels ? { labels: spec.labels } : {}),
277
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
278
- },
279
- spec: {
280
- ...cronJobSpec,
281
- jobTemplate,
282
- },
283
- });
284
- this.capture(cronJob, `${spec.name}-cronjob`);
285
- return cronJob;
286
- }
287
- addIngress(spec) {
288
- const ing = new ApiObject(this.chart, spec.name, {
289
- apiVersion: Rutter.NETWORKING_API_VERSION,
290
- kind: 'Ingress',
291
- metadata: {
292
- name: spec.name,
293
- ...(spec.labels ? { labels: spec.labels } : {}),
294
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
295
- },
296
- spec: {
297
- ingressClassName: spec.ingressClassName,
298
- defaultBackend: spec.defaultBackend,
299
- tls: spec.tls,
300
- rules: spec.rules.map((rule) => ({
301
- host: rule.host,
302
- http: {
303
- paths: rule.paths.map((path) => ({
304
- path: path.path,
305
- pathType: path.pathType,
306
- backend: path.backend,
307
- })),
308
- },
309
- })),
310
- },
311
- });
312
- this.capture(ing, `${spec.name}-ingress`);
313
- return ing;
314
- }
315
- addPersistentVolume(spec) {
316
- // Apply cloud provider optimizations
317
- const optimizedSpec = this.optimizePVForCloud(spec);
318
- const pvSpec = {
319
- capacity: { storage: optimizedSpec.capacity },
320
- accessModes: optimizedSpec.accessModes,
321
- storageClassName: optimizedSpec.storageClassName,
322
- volumeMode: optimizedSpec.volumeMode,
323
- persistentVolumeReclaimPolicy: optimizedSpec.reclaimPolicy,
324
- mountOptions: optimizedSpec.mountOptions,
325
- nodeAffinity: optimizedSpec.nodeAffinity,
326
- claimRef: optimizedSpec.claimRef,
327
- };
328
- // Handle known volume sources with validation
329
- const knownSources = [
330
- 'csi',
331
- 'nfs',
332
- 'awsElasticBlockStore',
333
- 'hostPath',
334
- 'azureDisk',
335
- 'azureFile',
336
- 'gcePersistentDisk',
337
- ];
338
- let sourceCount = 0;
339
- for (const sourceType of knownSources) {
340
- // eslint-disable-next-line security/detect-object-injection -- Safe: sourceType is from known const array
341
- const sourceValue = optimizedSpec.source[sourceType];
342
- if (sourceValue) {
343
- // eslint-disable-next-line security/detect-object-injection -- Safe: sourceType is from known const array
344
- pvSpec[sourceType] = sourceValue;
345
- sourceCount++;
346
- }
347
- }
348
- // Validate single volume source
349
- if (sourceCount === 0) {
350
- throw new Error(`PersistentVolume ${spec.name}: No volume source specified`);
351
- }
352
- if (sourceCount > 1) {
353
- throw new Error(`PersistentVolume ${spec.name}: Multiple volume sources specified, only one allowed`);
354
- }
355
- // Handle custom volume sources
356
- for (const [key, value] of Object.entries(optimizedSpec.source)) {
357
- if (!knownSources.includes(key) && value !== undefined) {
358
- // eslint-disable-next-line security/detect-object-injection -- Safe: controlled object construction
359
- pvSpec[key] = value;
360
- }
361
- }
362
- const pv = new ApiObject(this.chart, spec.name, {
363
- apiVersion: 'v1',
364
- kind: 'PersistentVolume',
365
- metadata: {
366
- name: spec.name,
367
- ...(optimizedSpec.labels ? { labels: optimizedSpec.labels } : {}),
368
- ...(optimizedSpec.annotations ? { annotations: optimizedSpec.annotations } : {}),
369
- },
370
- spec: pvSpec,
371
- });
372
- this.capture(pv, `${spec.name}-pv`);
373
- return pv;
374
- }
375
- optimizePVForCloud(spec) {
376
- const optimized = { ...spec };
377
- const EBS_CSI_DRIVER = 'ebs.csi.aws.com';
378
- // Apply cloud-specific optimizations
379
- if (spec.cloudProvider === 'aws') {
380
- optimized.annotations = {
381
- ...optimized.annotations,
382
- 'volume.beta.kubernetes.io/storage-provisioner': EBS_CSI_DRIVER,
383
- };
384
- // Default to gp3 for better performance/cost
385
- if (spec.source.csi?.driver === EBS_CSI_DRIVER && optimized.source.csi) {
386
- optimized.source.csi.volumeAttributes = {
387
- type: 'gp3',
388
- ...spec.source.csi.volumeAttributes,
389
- };
390
- }
391
- }
392
- else if (spec.cloudProvider === 'azure') {
393
- optimized.annotations = {
394
- ...optimized.annotations,
395
- [Rutter.AZURE_DISK_PROVISIONER_ANNOTATION]: Rutter.AZURE_DISK_CSI_DRIVER,
396
- };
397
- // Default to Premium_ZRS for multi-zone clusters
398
- if (spec.source.csi?.driver === Rutter.AZURE_DISK_CSI_DRIVER && optimized.source.csi) {
399
- optimized.source.csi.volumeAttributes = {
400
- skuName: 'Premium_ZRS',
401
- ...spec.source.csi.volumeAttributes,
402
- };
403
- }
404
- }
405
- else if (spec.cloudProvider === 'gcp') {
406
- optimized.annotations = {
407
- ...optimized.annotations,
408
- 'volume.beta.kubernetes.io/storage-provisioner': 'pd.csi.storage.gke.io',
409
- };
410
- }
411
- // Set sensible defaults
412
- optimized.reclaimPolicy = optimized.reclaimPolicy ?? 'Delete';
413
- optimized.volumeMode = optimized.volumeMode ?? 'Filesystem';
414
- return optimized;
415
- }
416
- addPersistentVolumeClaim(spec) {
417
- // Validate access modes compatibility
418
- this.validatePVCAccessModes(spec);
419
- const pvc = new ApiObject(this.chart, spec.name, {
420
- apiVersion: 'v1',
421
- kind: 'PersistentVolumeClaim',
422
- metadata: {
423
- name: spec.name,
424
- ...(spec.labels ? { labels: spec.labels } : {}),
425
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
426
- },
427
- spec: {
428
- accessModes: spec.accessModes,
429
- resources: spec.resources,
430
- storageClassName: spec.storageClassName,
431
- volumeMode: spec.volumeMode ?? 'Filesystem',
432
- selector: spec.selector,
433
- volumeName: spec.volumeName,
434
- },
435
- });
436
- this.capture(pvc, `${spec.name}-pvc`);
437
- return pvc;
438
- }
439
- validatePVCAccessModes(spec) {
440
- const { accessModes, storageClassName } = spec;
441
- // Validate ReadWriteOncePod (K8s 1.22+)
442
- if (accessModes.includes('ReadWriteOncePod') && accessModes.length > 1) {
443
- throw new Error(`PVC ${spec.name}: ReadWriteOncePod cannot be combined with other access modes`);
444
- }
445
- // Warn about common misconfigurations
446
- if (storageClassName?.includes('azure-disk') && accessModes.includes('ReadWriteMany')) {
447
- console.warn(`PVC ${spec.name}: Azure Disk does not support ReadWriteMany, consider Azure Files`);
448
- }
449
- if (storageClassName?.includes('ebs') && accessModes.includes('ReadWriteMany')) {
450
- console.warn(`PVC ${spec.name}: EBS does not support ReadWriteMany, consider EFS`);
451
- }
452
- }
453
- addConfigMap(spec) {
454
- const cm = new ApiObject(this.chart, spec.name, {
455
- apiVersion: 'v1',
456
- kind: 'ConfigMap',
457
- metadata: {
458
- name: spec.name,
459
- ...(spec.labels ? { labels: spec.labels } : {}),
460
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
461
- },
462
- data: spec.data,
463
- binaryData: spec.binaryData,
464
- immutable: spec.immutable,
465
- });
466
- this.capture(cm, `${spec.name}-configmap`);
467
- return cm;
468
- }
469
- addSecret(spec) {
470
- const sec = new ApiObject(this.chart, spec.name, {
471
- apiVersion: 'v1',
472
- kind: 'Secret',
473
- metadata: {
474
- name: spec.name,
475
- ...(spec.labels ? { labels: spec.labels } : {}),
476
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
477
- },
478
- type: spec.type ?? 'Opaque',
479
- stringData: spec.stringData,
480
- data: spec.data,
481
- immutable: spec.immutable,
482
- });
483
- this.capture(sec, `${spec.name}-secret`);
484
- return sec;
485
- }
486
- addServiceAccount(spec) {
487
- const annotations = this.buildServiceAccountAnnotations(spec);
488
- const sa = new ApiObject(this.chart, spec.name, {
489
- apiVersion: 'v1',
490
- kind: 'ServiceAccount',
491
- metadata: {
492
- name: spec.name,
493
- ...(Object.keys(annotations).length ? { annotations } : {}),
494
- ...(spec.labels ? { labels: spec.labels } : {}),
495
- },
496
- automountServiceAccountToken: spec.automountServiceAccountToken,
497
- imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
498
- secrets: spec.secrets?.map((n) => ({ name: n })),
499
- });
500
- this.capture(sa, `${spec.name}-serviceaccount`);
501
- return sa;
502
- }
503
- buildServiceAccountAnnotations(spec) {
504
- const annotations = {
505
- ...(spec.annotations ?? {}),
506
- };
507
- this.addAWSIRSAAnnotations(annotations, spec);
508
- this.addAzureWorkloadIdentityAnnotations(annotations, spec);
509
- this.addGCPWorkloadIdentityAnnotations(annotations, spec);
510
- return annotations;
511
- }
512
- addAWSIRSAAnnotations(annotations, spec) {
513
- if (spec.awsRoleArn) {
514
- annotations['eks.amazonaws.com/role-arn'] = spec.awsRoleArn;
515
- }
516
- if (spec.awsAudience) {
517
- annotations['eks.amazonaws.com/audience'] = spec.awsAudience;
518
- }
519
- if (spec.awsStsEndpointType) {
520
- annotations['eks.amazonaws.com/sts-regional-endpoints'] = spec.awsStsEndpointType;
521
- }
522
- if (spec.awsTokenExpiration !== undefined) {
523
- annotations['eks.amazonaws.com/token-expiration'] = String(spec.awsTokenExpiration);
524
- }
525
- }
526
- addAzureWorkloadIdentityAnnotations(annotations, spec) {
527
- if (spec.azureClientId) {
528
- annotations['azure.workload.identity/client-id'] = spec.azureClientId;
529
- }
530
- if (spec.azureTenantId) {
531
- annotations['azure.workload.identity/tenant-id'] = spec.azureTenantId;
532
- }
533
- if (spec.azureServiceAccountTokenExpiration !== undefined) {
534
- annotations['azure.workload.identity/service-account-token-expiration'] = String(spec.azureServiceAccountTokenExpiration);
535
- }
536
- }
537
- addGCPWorkloadIdentityAnnotations(annotations, spec) {
538
- if (spec.gcpServiceAccountEmail) {
539
- annotations['iam.gke.io/gcp-service-account'] = spec.gcpServiceAccountEmail;
540
- }
541
- }
542
- addHorizontalPodAutoscaler(spec) {
543
- // Default metrics if none provided (CPU utilization at 80%)
544
- const defaultMetrics = [
545
- {
546
- type: 'Resource',
547
- resource: {
548
- name: 'cpu',
549
- target: {
550
- type: 'Utilization',
551
- averageUtilization: 80,
552
- },
553
- },
554
- },
555
- ];
556
- const hpa = new ApiObject(this.chart, spec.name, {
557
- apiVersion: 'autoscaling/v2',
558
- kind: 'HorizontalPodAutoscaler',
559
- metadata: {
560
- name: spec.name,
561
- ...(spec.labels ? { labels: spec.labels } : {}),
562
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
563
- },
564
- spec: {
565
- scaleTargetRef: spec.scaleTargetRef,
566
- minReplicas: spec.minReplicas ?? 1,
567
- maxReplicas: spec.maxReplicas,
568
- metrics: spec.metrics ?? defaultMetrics,
569
- ...(spec.behavior ? { behavior: spec.behavior } : {}),
570
- },
571
- });
572
- this.capture(hpa, `${spec.name}-hpa`);
573
- return hpa;
574
- }
575
- addVerticalPodAutoscaler(spec) {
576
- const vpa = new ApiObject(this.chart, spec.name, {
577
- apiVersion: 'autoscaling.k8s.io/v1',
578
- kind: 'VerticalPodAutoscaler',
579
- metadata: {
580
- name: spec.name,
581
- ...(spec.labels ? { labels: spec.labels } : {}),
582
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
583
- },
584
- spec: {
585
- targetRef: spec.targetRef,
586
- updatePolicy: spec.updatePolicy ?? { updateMode: 'Auto' },
587
- ...(spec.resourcePolicy ? { resourcePolicy: spec.resourcePolicy } : {}),
588
- ...(spec.recommenders ? { recommenders: spec.recommenders } : {}),
589
- },
590
- });
591
- this.capture(vpa, `${spec.name}-vpa`);
592
- return vpa;
593
- }
594
- addPodDisruptionBudget(spec) {
595
- const pdb = new ApiObject(this.chart, spec.name, {
596
- apiVersion: 'policy/v1',
597
- kind: 'PodDisruptionBudget',
598
- metadata: {
599
- name: spec.name,
600
- ...(spec.labels ? { labels: spec.labels } : {}),
601
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
602
- },
603
- spec: {
604
- ...(spec.minAvailable !== undefined ? { minAvailable: spec.minAvailable } : {}),
605
- ...(spec.maxUnavailable !== undefined ? { maxUnavailable: spec.maxUnavailable } : {}),
606
- selector: spec.selector,
607
- },
608
- });
609
- this.capture(pdb, `${spec.name}-pdb`);
610
- return pdb;
611
- }
612
- addAWSEBSStorageClass(spec) {
613
- const DEFAULT_VOLUME_TYPE = 'gp3';
614
- const parameters = {
615
- type: spec.volumeType ?? DEFAULT_VOLUME_TYPE,
616
- fsType: spec.fsType ?? 'ext4',
617
- encrypted: String(spec.encrypted ?? true),
618
- };
619
- if (spec.kmsKeyId) {
620
- parameters['kmsKeyId'] = spec.kmsKeyId;
621
- }
622
- const IOPS_SUPPORTED_TYPES = ['gp3', 'io1', 'io2'];
623
- if (spec.iops && IOPS_SUPPORTED_TYPES.includes(spec.volumeType ?? DEFAULT_VOLUME_TYPE)) {
624
- parameters['iops'] = String(spec.iops);
625
- }
626
- if (spec.throughput && spec.volumeType === DEFAULT_VOLUME_TYPE) {
627
- parameters['throughput'] = String(spec.throughput);
628
- }
629
- const sc = new ApiObject(this.chart, spec.name, {
630
- apiVersion: Rutter.STORAGE_API_VERSION,
631
- kind: 'StorageClass',
632
- metadata: {
633
- name: spec.name,
634
- ...(spec.labels ? { labels: spec.labels } : {}),
635
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
636
- },
637
- provisioner: 'ebs.csi.aws.com',
638
- parameters,
639
- reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
640
- allowVolumeExpansion: spec.allowVolumeExpansion ?? true,
641
- volumeBindingMode: spec.volumeBindingMode ?? 'WaitForFirstConsumer',
642
- });
643
- this.capture(sc, `${spec.name}-storageclass`);
644
- return sc;
645
- }
646
- addAWSEBSPersistentVolumeClaim(spec) {
647
- const pvc = new ApiObject(this.chart, spec.name, {
648
- apiVersion: 'v1',
649
- kind: 'PersistentVolumeClaim',
650
- metadata: {
651
- name: spec.name,
652
- ...(spec.labels ? { labels: spec.labels } : {}),
653
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
654
- },
655
- spec: {
656
- accessModes: spec.accessModes ?? ['ReadWriteOnce'],
657
- storageClassName: spec.storageClassName,
658
- resources: {
659
- requests: {
660
- storage: spec.size,
661
- },
662
- },
663
- },
664
- });
665
- this.capture(pvc, `${spec.name}-pvc`);
666
- return pvc;
667
- }
668
- addAWSEFSStorageClass(spec) {
669
- const sc = new ApiObject(this.chart, spec.name, {
670
- apiVersion: Rutter.STORAGE_API_VERSION,
671
- kind: 'StorageClass',
672
- metadata: {
673
- name: spec.name,
674
- ...(spec.labels ? { labels: spec.labels } : {}),
675
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
676
- },
677
- provisioner: 'efs.csi.aws.com',
678
- reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
679
- volumeBindingMode: spec.volumeBindingMode ?? 'Immediate',
680
- });
681
- this.capture(sc, `${spec.name}-storageclass`);
682
- return sc;
683
- }
684
- addAWSEFSPersistentVolume(spec) {
685
- const csiSpec = {
686
- driver: 'efs.csi.aws.com',
687
- volumeHandle: spec.fileSystemId,
688
- };
689
- if (spec.accessPoint) {
690
- csiSpec['volumeHandle'] = `${spec.fileSystemId}::${spec.accessPoint}`;
691
- }
692
- const volumeAttributes = {};
693
- if (spec.directoryPerms) {
694
- volumeAttributes['directoryPerms'] = spec.directoryPerms;
695
- }
696
- if (spec.gidRangeStart !== undefined) {
697
- volumeAttributes['gidRangeStart'] = String(spec.gidRangeStart);
698
- }
699
- if (spec.gidRangeEnd !== undefined) {
700
- volumeAttributes['gidRangeEnd'] = String(spec.gidRangeEnd);
701
- }
702
- if (spec.basePath) {
703
- volumeAttributes['basePath'] = spec.basePath;
704
- }
705
- if (spec.subPath) {
706
- volumeAttributes['subPath'] = spec.subPath;
707
- }
708
- if (Object.keys(volumeAttributes).length > 0) {
709
- csiSpec['volumeAttributes'] = volumeAttributes;
710
- }
711
- const pv = new ApiObject(this.chart, spec.name, {
712
- apiVersion: 'v1',
713
- kind: 'PersistentVolume',
714
- metadata: {
715
- name: spec.name,
716
- ...(spec.labels ? { labels: spec.labels } : {}),
717
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
718
- },
719
- spec: {
720
- capacity: {
721
- storage: spec.capacity ?? '5Gi',
722
- },
723
- accessModes: spec.accessModes ?? ['ReadWriteMany'],
724
- persistentVolumeReclaimPolicy: 'Retain',
725
- storageClassName: spec.storageClassName,
726
- csi: csiSpec,
727
- },
728
- });
729
- this.capture(pv, `${spec.name}-pv`);
730
- return pv;
731
- }
732
- addAWSEFSPersistentVolumeClaim(spec) {
733
- const pvc = new ApiObject(this.chart, spec.name, {
734
- apiVersion: 'v1',
735
- kind: 'PersistentVolumeClaim',
736
- metadata: {
737
- name: spec.name,
738
- ...(spec.labels ? { labels: spec.labels } : {}),
739
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
740
- },
741
- spec: {
742
- accessModes: spec.accessModes ?? ['ReadWriteMany'],
743
- storageClassName: spec.storageClassName,
744
- resources: {
745
- requests: {
746
- storage: spec.size ?? '5Gi',
747
- },
748
- },
749
- },
750
- });
751
- this.capture(pvc, `${spec.name}-pvc`);
752
- return pvc;
753
- }
754
- addAWSALBIngress(spec) {
755
- const annotations = this.buildALBAnnotations(spec);
756
- const ingress = new ApiObject(this.chart, spec.name, {
757
- apiVersion: Rutter.NETWORKING_API_VERSION,
758
- kind: 'Ingress',
759
- metadata: {
760
- name: spec.name,
761
- ...(spec.labels ? { labels: spec.labels } : {}),
762
- annotations,
763
- },
764
- spec: {
765
- tls: spec.tls,
766
- rules: spec.rules.map((rule) => ({
767
- host: rule.host,
768
- http: {
769
- paths: rule.paths.map((path) => ({
770
- path: path.path,
771
- pathType: path.pathType,
772
- backend: path.backend,
773
- })),
774
- },
775
- })),
776
- },
777
- });
778
- this.capture(ingress, `${spec.name}-ingress`);
779
- return ingress;
780
- }
781
- buildALBAnnotations(spec) {
782
- const annotations = {
783
- 'kubernetes.io/ingress.class': 'alb',
784
- 'alb.ingress.kubernetes.io/scheme': spec.scheme ?? 'internet-facing',
785
- 'alb.ingress.kubernetes.io/target-type': spec.targetType ?? 'ip',
786
- ...(spec.annotations ?? {}),
787
- };
788
- this.addOptionalALBAnnotations(annotations, spec);
789
- this.addHealthCheckAnnotations(annotations, spec);
790
- this.addTagsAnnotation(annotations, spec);
791
- return annotations;
792
- }
793
- addOptionalALBAnnotations(annotations, spec) {
794
- if (spec.ipAddressType) {
795
- annotations['alb.ingress.kubernetes.io/ip-address-type'] = spec.ipAddressType;
796
- }
797
- if (spec.groupName) {
798
- annotations['alb.ingress.kubernetes.io/group.name'] = spec.groupName;
799
- }
800
- if (spec.groupOrder !== undefined) {
801
- annotations['alb.ingress.kubernetes.io/group.order'] = String(spec.groupOrder);
802
- }
803
- if (spec.subnets && spec.subnets.length > 0) {
804
- annotations['alb.ingress.kubernetes.io/subnets'] = spec.subnets.join(',');
805
- }
806
- if (spec.securityGroups && spec.securityGroups.length > 0) {
807
- annotations['alb.ingress.kubernetes.io/security-groups'] = spec.securityGroups.join(',');
808
- }
809
- if (spec.certificateArn) {
810
- annotations['alb.ingress.kubernetes.io/certificate-arn'] = spec.certificateArn;
811
- }
812
- if (spec.sslRedirect) {
813
- annotations['alb.ingress.kubernetes.io/ssl-redirect'] = '443';
814
- }
815
- }
816
- addHealthCheckAnnotations(annotations, spec) {
817
- if (spec.healthCheckPath) {
818
- annotations['alb.ingress.kubernetes.io/healthcheck-path'] = spec.healthCheckPath;
819
- }
820
- if (spec.healthCheckIntervalSeconds !== undefined) {
821
- annotations['alb.ingress.kubernetes.io/healthcheck-interval-seconds'] = String(spec.healthCheckIntervalSeconds);
822
- }
823
- if (spec.healthCheckTimeoutSeconds !== undefined) {
824
- annotations['alb.ingress.kubernetes.io/healthcheck-timeout-seconds'] = String(spec.healthCheckTimeoutSeconds);
825
- }
826
- if (spec.healthyThresholdCount !== undefined) {
827
- annotations['alb.ingress.kubernetes.io/healthy-threshold-count'] = String(spec.healthyThresholdCount);
828
- }
829
- if (spec.unhealthyThresholdCount !== undefined) {
830
- annotations['alb.ingress.kubernetes.io/unhealthy-threshold-count'] = String(spec.unhealthyThresholdCount);
831
- }
832
- }
833
- addTagsAnnotation(annotations, spec) {
834
- if (spec.tags && Object.keys(spec.tags).length > 0) {
835
- const tagString = this.formatCloudResourceTags(spec.tags);
836
- annotations['alb.ingress.kubernetes.io/tags'] = tagString;
837
- }
838
- }
839
- /**
840
- * Add Azure Application Gateway Ingress Controller (AGIC) Ingress.
841
- * Creates an Ingress resource with AGIC-specific annotations for AKS deployments.
842
- *
843
- * @param spec - AGIC Ingress specification
844
- * @returns The created Ingress ApiObject
845
- *
846
- * @example
847
- * ```typescript
848
- * rutter.addAzureAGICIngress({
849
- * name: 'app-ingress',
850
- * rules: [{
851
- * host: 'app.example.com',
852
- * paths: [{
853
- * path: '/',
854
- * pathType: 'Prefix',
855
- * backend: { service: { name: 'app-service', port: { number: 80 } } }
856
- * }]
857
- * }],
858
- * sslRedirect: true,
859
- * backendProtocol: 'https',
860
- * healthProbePath: '/health'
861
- * });
862
- * ```
863
- */
864
- addAzureAGICIngress(spec) {
865
- this.validateAGICHealthProbeParams(spec);
866
- this.validateAGICSecurityParams(spec);
867
- this.validateAGICAnnotationConsistency(spec);
868
- const annotations = this.buildAGICAnnotations(spec);
869
- const ingress = new ApiObject(this.chart, spec.name, {
870
- apiVersion: Rutter.NETWORKING_API_VERSION,
871
- kind: 'Ingress',
872
- metadata: {
873
- name: spec.name,
874
- annotations,
875
- ...(spec.labels ? { labels: spec.labels } : {}),
876
- },
877
- spec: {
878
- tls: spec.tls,
879
- rules: spec.rules.map((rule) => ({
880
- host: rule.host,
881
- http: {
882
- paths: rule.paths.map((path) => ({
883
- path: path.path,
884
- pathType: path.pathType,
885
- backend: path.backend,
886
- })),
887
- },
888
- })),
889
- },
890
- });
891
- this.capture(ingress, `${spec.name}-ingress`);
892
- return ingress;
893
- }
894
- buildAGICAnnotations(spec) {
895
- const annotations = {
896
- 'kubernetes.io/ingress.class': 'azure/application-gateway',
897
- ...(spec.annotations ?? {}),
898
- };
899
- this.addAGICPathAnnotations(annotations, spec);
900
- this.addAGICProtocolAnnotations(annotations, spec);
901
- this.addAGICHealthProbeAnnotations(annotations, spec);
902
- this.addAGICConnectionAnnotations(annotations, spec);
903
- this.addAGICSecurityAnnotations(annotations, spec);
904
- this.addAGICAdvancedAnnotations(annotations, spec);
905
- return annotations;
906
- }
907
- addAGICPathAnnotations(annotations, spec) {
908
- if (spec.backendPathPrefix) {
909
- annotations['appgw.ingress.kubernetes.io/backend-path-prefix'] = spec.backendPathPrefix;
910
- }
911
- if (spec.backendHostname) {
912
- annotations['appgw.ingress.kubernetes.io/backend-hostname'] = spec.backendHostname;
913
- }
914
- }
915
- addAGICProtocolAnnotations(annotations, spec) {
916
- if (spec.backendProtocol) {
917
- annotations['appgw.ingress.kubernetes.io/backend-protocol'] = spec.backendProtocol;
918
- }
919
- if (spec.sslRedirect) {
920
- annotations['appgw.ingress.kubernetes.io/ssl-redirect'] = 'true';
921
- }
922
- if (spec.usePrivateIp) {
923
- annotations['appgw.ingress.kubernetes.io/use-private-ip'] = 'true';
924
- }
925
- if (spec.overrideFrontendPort !== undefined) {
926
- annotations['appgw.ingress.kubernetes.io/override-frontend-port'] = String(spec.overrideFrontendPort);
927
- }
928
- }
929
- addAGICHealthProbeAnnotations(annotations, spec) {
930
- if (spec.healthProbeHostname) {
931
- annotations['appgw.ingress.kubernetes.io/health-probe-hostname'] = spec.healthProbeHostname;
932
- }
933
- if (spec.healthProbePort !== undefined) {
934
- annotations['appgw.ingress.kubernetes.io/health-probe-port'] = String(spec.healthProbePort);
935
- }
936
- if (spec.healthProbePath) {
937
- annotations['appgw.ingress.kubernetes.io/health-probe-path'] = spec.healthProbePath;
938
- }
939
- if (spec.healthProbeStatusCodes) {
940
- annotations['appgw.ingress.kubernetes.io/health-probe-status-codes'] =
941
- spec.healthProbeStatusCodes;
942
- }
943
- if (spec.healthProbeInterval !== undefined) {
944
- annotations['appgw.ingress.kubernetes.io/health-probe-interval'] = String(spec.healthProbeInterval);
945
- }
946
- if (spec.healthProbeTimeout !== undefined) {
947
- annotations['appgw.ingress.kubernetes.io/health-probe-timeout'] = String(spec.healthProbeTimeout);
948
- }
949
- if (spec.healthProbeUnhealthyThreshold !== undefined) {
950
- annotations['appgw.ingress.kubernetes.io/health-probe-unhealthy-threshold'] = String(spec.healthProbeUnhealthyThreshold);
951
- }
952
- }
953
- addAGICConnectionAnnotations(annotations, spec) {
954
- if (spec.cookieBasedAffinity) {
955
- annotations['appgw.ingress.kubernetes.io/cookie-based-affinity'] = 'true';
956
- }
957
- if (spec.requestTimeout !== undefined) {
958
- annotations['appgw.ingress.kubernetes.io/request-timeout'] = String(spec.requestTimeout);
959
- }
960
- if (spec.connectionDraining) {
961
- annotations['appgw.ingress.kubernetes.io/connection-draining'] = 'true';
962
- }
963
- if (spec.connectionDrainingTimeout !== undefined) {
964
- annotations['appgw.ingress.kubernetes.io/connection-draining-timeout'] = String(spec.connectionDrainingTimeout);
965
- }
966
- }
967
- addAGICSecurityAnnotations(annotations, spec) {
968
- if (spec.appgwSslCertificate) {
969
- annotations['appgw.ingress.kubernetes.io/appgw-ssl-certificate'] = spec.appgwSslCertificate;
970
- }
971
- if (spec.appgwSslProfile) {
972
- annotations['appgw.ingress.kubernetes.io/appgw-ssl-profile'] = spec.appgwSslProfile;
973
- }
974
- if (spec.appgwTrustedRootCertificate && spec.appgwTrustedRootCertificate.length > 0) {
975
- annotations['appgw.ingress.kubernetes.io/appgw-trusted-root-certificate'] =
976
- spec.appgwTrustedRootCertificate.join(',');
977
- }
978
- if (spec.wafPolicyForPath) {
979
- annotations['appgw.ingress.kubernetes.io/waf-policy-for-path'] = spec.wafPolicyForPath;
980
- }
981
- }
982
- addAGICAdvancedAnnotations(annotations, spec) {
983
- if (spec.hostnameExtension && spec.hostnameExtension.length > 0) {
984
- annotations['appgw.ingress.kubernetes.io/hostname-extension'] =
985
- spec.hostnameExtension.join(', ');
986
- }
987
- if (spec.rewriteRuleSet) {
988
- annotations['appgw.ingress.kubernetes.io/rewrite-rule-set'] = spec.rewriteRuleSet;
989
- }
990
- if (spec.rulePriority !== undefined) {
991
- annotations['appgw.ingress.kubernetes.io/rule-priority'] = String(spec.rulePriority);
992
- }
993
- }
994
- addAWSIRSAServiceAccount(spec) {
995
- const annotations = {
996
- 'eks.amazonaws.com/role-arn': spec.roleArn,
997
- ...(spec.audience ? { 'eks.amazonaws.com/audience': spec.audience } : {}),
998
- ...(spec.stsEndpointType
999
- ? { 'eks.amazonaws.com/sts-regional-endpoints': spec.stsEndpointType }
1000
- : { 'eks.amazonaws.com/sts-regional-endpoints': 'regional' }),
1001
- ...(spec.tokenExpiration !== undefined
1002
- ? { 'eks.amazonaws.com/token-expiration': String(spec.tokenExpiration) }
1003
- : {}),
1004
- ...(spec.annotations ?? {}),
1005
- };
1006
- const sa = new ApiObject(this.chart, spec.name, {
1007
- apiVersion: 'v1',
1008
- kind: 'ServiceAccount',
1009
- metadata: {
1010
- name: spec.name,
1011
- annotations,
1012
- ...(spec.labels ? { labels: spec.labels } : {}),
1013
- },
1014
- automountServiceAccountToken: spec.automountServiceAccountToken,
1015
- imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
1016
- secrets: spec.secrets?.map((n) => ({ name: n })),
1017
- });
1018
- this.capture(sa, `${spec.name}-serviceaccount`);
1019
- return sa;
1020
- }
1021
- addAWSSecretProviderClass(spec) {
1022
- const objects = spec.objects.map((obj) => {
1023
- const baseObj = {
1024
- objectName: obj.objectName,
1025
- objectType: obj.objectType,
1026
- };
1027
- if ('objectAlias' in obj && obj.objectAlias) {
1028
- baseObj['objectAlias'] = obj.objectAlias;
1029
- }
1030
- if ('jmesPath' in obj && obj.jmesPath) {
1031
- baseObj['jmesPath'] = obj.jmesPath;
1032
- }
1033
- return baseObj;
1034
- });
1035
- const objectsYaml = YAML.stringify(objects, { indent: 2 }).trim();
1036
- const parameters = {
1037
- objects: `|\n ${objectsYaml.split('\n').join('\n ')}`,
1038
- };
1039
- if (spec.region) {
1040
- parameters['region'] = spec.region;
1041
- }
1042
- const spc = new ApiObject(this.chart, spec.name, {
1043
- apiVersion: 'secrets-store.csi.x-k8s.io/v1',
1044
- kind: 'SecretProviderClass',
1045
- metadata: {
1046
- name: spec.name,
1047
- ...(spec.labels ? { labels: spec.labels } : {}),
1048
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1049
- },
1050
- spec: {
1051
- provider: 'aws',
1052
- parameters,
1053
- },
1054
- });
1055
- this.capture(spc, `${spec.name}-secretproviderclass`);
1056
- return spc;
1057
- }
1058
- /**
1059
- * Add Azure Workload Identity ServiceAccount.
1060
- * Creates a ServiceAccount with Azure Workload Identity annotations for secure authentication.
1061
- *
1062
- * @param spec - Azure Workload Identity ServiceAccount specification
1063
- * @returns The created ServiceAccount ApiObject
1064
- *
1065
- * @example
1066
- * ```typescript
1067
- * rutter.addAzureWorkloadIdentityServiceAccount({
1068
- * name: 'workload-identity-sa',
1069
- * clientId: '12345678-1234-1234-1234-123456789012',
1070
- * tenantId: '87654321-4321-4321-4321-210987654321',
1071
- * tokenExpiration: 3600
1072
- * });
1073
- * ```
1074
- */
1075
- addAzureWorkloadIdentityServiceAccount(spec) {
1076
- // Validate all inputs first for security
1077
- if (spec.tokenExpiration !== undefined &&
1078
- (spec.tokenExpiration < 3600 || spec.tokenExpiration > 86400)) {
1079
- throw new Error(`Azure Workload Identity ${spec.name}: tokenExpiration must be between 3600 and 86400 seconds`);
1080
- }
1081
- if (!spec.clientId?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
1082
- throw new Error(`Azure Workload Identity ${spec.name}: Invalid client ID format`);
1083
- }
1084
- if (!spec.tenantId?.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)) {
1085
- throw new Error(`Azure Workload Identity ${spec.name}: Invalid tenant ID format`);
1086
- }
1087
- const annotations = {
1088
- 'azure.workload.identity/client-id': spec.clientId,
1089
- 'azure.workload.identity/tenant-id': spec.tenantId,
1090
- ...(spec.tokenExpiration !== undefined
1091
- ? {
1092
- 'azure.workload.identity/service-account-token-expiration': String(spec.tokenExpiration),
1093
- }
1094
- : {}),
1095
- ...(spec.annotations ?? {}),
1096
- };
1097
- const sa = new ApiObject(this.chart, spec.name, {
1098
- apiVersion: 'v1',
1099
- kind: 'ServiceAccount',
1100
- metadata: {
1101
- name: spec.name,
1102
- annotations,
1103
- ...(spec.labels ? { labels: spec.labels } : {}),
1104
- },
1105
- automountServiceAccountToken: spec.automountServiceAccountToken,
1106
- imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
1107
- secrets: spec.secrets?.map((n) => ({ name: n })),
1108
- });
1109
- this.capture(sa, `${spec.name}-serviceaccount`);
1110
- return sa;
1111
- }
1112
- /**
1113
- * Add Azure Key Vault SecretProviderClass.
1114
- * Creates a SecretProviderClass for mounting secrets from Azure Key Vault using CSI driver.
1115
- *
1116
- * @param spec - Azure Key Vault SecretProviderClass specification
1117
- * @returns The created SecretProviderClass ApiObject
1118
- *
1119
- * @example
1120
- * ```typescript
1121
- * rutter.addAzureKeyVaultSecretProviderClass({
1122
- * name: 'app-secrets',
1123
- * keyVaultName: 'my-keyvault',
1124
- * tenantId: '87654321-4321-4321-4321-210987654321',
1125
- * objects: [
1126
- * { objectName: 'database-password', objectType: 'secret' },
1127
- * { objectName: 'api-key', objectType: 'secret', objectAlias: 'API_KEY' }
1128
- * ],
1129
- * userAssignedIdentityID: '12345678-1234-1234-1234-123456789012'
1130
- * });
1131
- * ```
1132
- */
1133
- addAzureKeyVaultSecretProviderClass(spec) {
1134
- // Validate object names for security - prevent path traversal attacks
1135
- spec.objects.forEach((obj) => {
1136
- if (obj.objectName.includes('../') || obj.objectName.includes('..\\')) {
1137
- throw new Error(`Azure Key Vault ${spec.name}: Object name '${obj.objectName}' contains invalid path traversal sequences`);
1138
- }
1139
- });
1140
- const objects = spec.objects.map((obj) => {
1141
- const baseObj = {
1142
- objectName: obj.objectName,
1143
- objectType: obj.objectType,
1144
- };
1145
- if (obj.objectAlias) {
1146
- baseObj['objectAlias'] = obj.objectAlias;
1147
- }
1148
- if (obj.objectVersion) {
1149
- baseObj['objectVersion'] = obj.objectVersion;
1150
- }
1151
- return baseObj;
1152
- });
1153
- const objectsYaml = YAML.stringify(objects, { indent: 2 }).trim();
1154
- const parameters = {
1155
- keyvaultName: spec.keyVaultName,
1156
- tenantId: spec.tenantId,
1157
- objects: `|\n ${objectsYaml.split('\n').join('\n ')}`,
1158
- };
1159
- if (spec.userAssignedIdentityID) {
1160
- parameters['userAssignedIdentityID'] = spec.userAssignedIdentityID;
1161
- }
1162
- if (spec.cloudName) {
1163
- parameters['cloudName'] = spec.cloudName;
1164
- }
1165
- const spc = new ApiObject(this.chart, spec.name, {
1166
- apiVersion: 'secrets-store.csi.x-k8s.io/v1',
1167
- kind: 'SecretProviderClass',
1168
- metadata: {
1169
- name: spec.name,
1170
- ...(spec.labels ? { labels: spec.labels } : {}),
1171
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1172
- },
1173
- spec: {
1174
- provider: 'azure',
1175
- parameters,
1176
- },
1177
- });
1178
- this.capture(spc, `${spec.name}-secretproviderclass`);
1179
- return spc;
1180
- }
1181
- /**
1182
- * Add Azure Files StorageClass.
1183
- * Creates a StorageClass for dynamic provisioning of Azure Files volumes.
1184
- *
1185
- * @param spec - Azure Files StorageClass specification
1186
- * @returns The created StorageClass ApiObject
1187
- *
1188
- * @example
1189
- * ```typescript
1190
- * rutter.addAzureFilesStorageClass({
1191
- * name: 'azure-files-premium',
1192
- * skuName: 'Premium_LRS',
1193
- * protocol: 'smb',
1194
- * allowSharedAccess: true
1195
- * });
1196
- * ```
1197
- */
1198
- addAzureFilesStorageClass(spec) {
1199
- // Validate Azure Files parameters for security and compliance
1200
- this.validateAzureFilesParameters(spec);
1201
- const parameters = this.buildAzureFilesParameters(spec);
1202
- const sc = new ApiObject(this.chart, spec.name, {
1203
- apiVersion: Rutter.STORAGE_API_VERSION,
1204
- kind: 'StorageClass',
1205
- metadata: {
1206
- name: spec.name,
1207
- ...(spec.labels ? { labels: spec.labels } : {}),
1208
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1209
- },
1210
- provisioner: Rutter.AZURE_FILES_CSI_DRIVER,
1211
- parameters,
1212
- reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
1213
- allowVolumeExpansion: spec.allowVolumeExpansion ?? true,
1214
- volumeBindingMode: spec.volumeBindingMode ?? 'Immediate',
1215
- ...(spec.mountOptions ? { mountOptions: spec.mountOptions } : {}),
1216
- });
1217
- this.capture(sc, `${spec.name}-storageclass`);
1218
- return sc;
1219
- }
1220
- /**
1221
- * Add Azure Files PersistentVolume.
1222
- * Creates a PersistentVolume for static Azure Files volume provisioning.
1223
- *
1224
- * @param spec - Azure Files PersistentVolume specification
1225
- * @returns The created PersistentVolume ApiObject
1226
- */
1227
- addAzureFilesPersistentVolume(spec) {
1228
- const csiSpec = this.buildAzureFilesPVCSISpec(spec);
1229
- const pv = new ApiObject(this.chart, spec.name, {
1230
- apiVersion: 'v1',
1231
- kind: 'PersistentVolume',
1232
- metadata: {
1233
- name: spec.name,
1234
- ...(spec.labels ? { labels: spec.labels } : {}),
1235
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1236
- },
1237
- spec: {
1238
- capacity: { storage: spec.capacity },
1239
- accessModes: spec.accessModes ?? ['ReadWriteMany'],
1240
- persistentVolumeReclaimPolicy: spec.reclaimPolicy ?? 'Retain',
1241
- storageClassName: spec.storageClassName,
1242
- ...(spec.mountOptions ? { mountOptions: spec.mountOptions } : {}),
1243
- csi: csiSpec,
1244
- },
1245
- });
1246
- this.capture(pv, `${spec.name}-pv`);
1247
- return pv;
1248
- }
1249
- /**
1250
- * Add Azure Files PersistentVolumeClaim.
1251
- * Creates a PersistentVolumeClaim for Azure Files storage.
1252
- *
1253
- * @param spec - Azure Files PersistentVolumeClaim specification
1254
- * @returns The created PersistentVolumeClaim ApiObject
1255
- */
1256
- addAzureFilesPersistentVolumeClaim(spec) {
1257
- const pvc = new ApiObject(this.chart, spec.name, {
1258
- apiVersion: 'v1',
1259
- kind: 'PersistentVolumeClaim',
1260
- metadata: {
1261
- name: spec.name,
1262
- ...(spec.labels ? { labels: spec.labels } : {}),
1263
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1264
- },
1265
- spec: {
1266
- accessModes: spec.accessModes ?? ['ReadWriteMany'],
1267
- storageClassName: spec.storageClassName,
1268
- resources: {
1269
- requests: { storage: spec.size },
1270
- },
1271
- },
1272
- });
1273
- this.capture(pvc, `${spec.name}-pvc`);
1274
- return pvc;
1275
- }
1276
- /**
1277
- * Add Azure Container Registry ServiceAccount.
1278
- * Creates a ServiceAccount configured for ACR access.
1279
- *
1280
- * @param spec - Azure ACR ServiceAccount specification
1281
- * @returns The created ServiceAccount ApiObject
1282
- */
1283
- addAzureACRServiceAccount(spec) {
1284
- const annotations = {
1285
- ...(spec.annotations ?? {}),
1286
- };
1287
- // Add Workload Identity annotations if provided
1288
- if (spec.clientId) {
1289
- annotations['azure.workload.identity/client-id'] = spec.clientId;
1290
- }
1291
- if (spec.tenantId) {
1292
- annotations['azure.workload.identity/tenant-id'] = spec.tenantId;
1293
- }
1294
- const sa = new ApiObject(this.chart, spec.name, {
1295
- apiVersion: 'v1',
1296
- kind: 'ServiceAccount',
1297
- metadata: {
1298
- name: spec.name,
1299
- ...(Object.keys(annotations).length ? { annotations } : {}),
1300
- ...(spec.labels ? { labels: spec.labels } : {}),
1301
- },
1302
- automountServiceAccountToken: spec.automountServiceAccountToken,
1303
- imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
1304
- secrets: spec.secrets?.map((n) => ({ name: n })),
1305
- });
1306
- this.capture(sa, `${spec.name}-serviceaccount`);
1307
- return sa;
1308
- }
1309
- addAzureDiskStorageClass(spec) {
1310
- // Validate numeric parameters to prevent runtime errors
1311
- this.validateAzureDiskNumericParameters(spec);
1312
- const parameters = this.buildAzureDiskParameters(spec);
1313
- const sc = new ApiObject(this.chart, spec.name, {
1314
- apiVersion: Rutter.STORAGE_API_VERSION,
1315
- kind: 'StorageClass',
1316
- metadata: {
1317
- name: spec.name,
1318
- ...(spec.labels ? { labels: spec.labels } : {}),
1319
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1320
- },
1321
- provisioner: Rutter.AZURE_DISK_CSI_DRIVER,
1322
- parameters,
1323
- reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
1324
- allowVolumeExpansion: spec.allowVolumeExpansion ?? true,
1325
- volumeBindingMode: spec.volumeBindingMode ?? 'WaitForFirstConsumer',
1326
- });
1327
- this.capture(sc, `${spec.name}-storageclass`);
1328
- return sc;
1329
- }
1330
- buildAzureDiskParameters(spec) {
1331
- const parameters = {
1332
- skuName: spec.skuName ?? 'StandardSSD_LRS',
1333
- fsType: spec.fsType ?? 'ext4',
1334
- // Security best practices: default to private network access and encryption
1335
- networkAccessPolicy: spec.networkAccessPolicy ?? 'AllowPrivate',
1336
- };
1337
- this.addOptionalAzureDiskParameters(parameters, spec);
1338
- this.addAzureDiskEncryptionParameters(parameters, spec);
1339
- this.addAzureDiskPerformanceParameters(parameters, spec);
1340
- return parameters;
1341
- }
1342
- addOptionalAzureDiskParameters(parameters, spec) {
1343
- if (spec.cachingMode) {
1344
- parameters['cachingMode'] = spec.cachingMode;
1345
- }
1346
- if (spec.resourceGroup) {
1347
- parameters['resourceGroup'] = spec.resourceGroup;
1348
- }
1349
- if (spec.logicalSectorSize !== undefined) {
1350
- parameters['LogicalSectorSize'] = String(spec.logicalSectorSize);
1351
- }
1352
- if (spec.tags && Object.keys(spec.tags).length > 0) {
1353
- const tagString = this.formatCloudResourceTags(spec.tags);
1354
- parameters['tags'] = tagString;
1355
- }
1356
- // networkAccessPolicy is now set as default in buildAzureDiskParameters
1357
- if (spec.networkAccessPolicy && spec.networkAccessPolicy !== 'AllowPrivate') {
1358
- parameters['networkAccessPolicy'] = spec.networkAccessPolicy;
1359
- }
1360
- if (spec.diskAccessID) {
1361
- parameters['diskAccessID'] = spec.diskAccessID;
1362
- }
1363
- if (spec.maxShares !== undefined) {
1364
- parameters['maxShares'] = String(spec.maxShares);
1365
- }
1366
- }
1367
- addAzureDiskEncryptionParameters(parameters, spec) {
1368
- if (spec.diskEncryptionSetID) {
1369
- parameters['diskEncryptionSetID'] = spec.diskEncryptionSetID;
1370
- }
1371
- if (spec.diskEncryptionType) {
1372
- parameters['diskEncryptionType'] = spec.diskEncryptionType;
1373
- }
1374
- }
1375
- addAzureDiskPerformanceParameters(parameters, spec) {
1376
- if (spec.diskIOPSReadWrite !== undefined) {
1377
- parameters['DiskIOPSReadWrite'] = String(spec.diskIOPSReadWrite);
1378
- }
1379
- if (spec.diskMBpsReadWrite !== undefined) {
1380
- parameters['DiskMBpsReadWrite'] = String(spec.diskMBpsReadWrite);
1381
- }
1382
- if (spec.writeAcceleratorEnabled !== undefined) {
1383
- parameters['writeAcceleratorEnabled'] = String(spec.writeAcceleratorEnabled);
1384
- }
1385
- if (spec.enableBursting !== undefined) {
1386
- parameters['enableBursting'] = String(spec.enableBursting);
1387
- }
1388
- }
1389
- validateAzureDiskNumericParameters(spec) {
1390
- this.validateAzureDiskIOPS(spec);
1391
- this.validateAzureDiskThroughput(spec);
1392
- this.validateAzureDiskShares(spec);
1393
- this.validateAzureDiskSectorSize(spec);
1394
- }
1395
- validateAzureDiskIOPS(spec) {
1396
- if (spec.diskIOPSReadWrite === undefined)
1397
- return;
1398
- if (spec.diskIOPSReadWrite < 100) {
1399
- throw new Error(`Azure Disk ${spec.name}: diskIOPSReadWrite must be at least 100 IOPS`);
1400
- }
1401
- if (spec.diskIOPSReadWrite > 400000) {
1402
- throw new Error(`Azure Disk ${spec.name}: diskIOPSReadWrite cannot exceed 400,000 IOPS`);
1403
- }
1404
- }
1405
- validateAzureDiskThroughput(spec) {
1406
- if (spec.diskMBpsReadWrite === undefined)
1407
- return;
1408
- if (spec.diskMBpsReadWrite < 1) {
1409
- throw new Error(`Azure Disk ${spec.name}: diskMBpsReadWrite must be at least 1 MB/s`);
1410
- }
1411
- if (spec.diskMBpsReadWrite > 10000) {
1412
- throw new Error(`Azure Disk ${spec.name}: diskMBpsReadWrite cannot exceed 10,000 MB/s`);
1413
- }
1414
- }
1415
- validateAzureDiskShares(spec) {
1416
- if (spec.maxShares === undefined)
1417
- return;
1418
- if (spec.maxShares < 1) {
1419
- throw new Error(`Azure Disk ${spec.name}: maxShares must be at least 1`);
1420
- }
1421
- if (spec.maxShares > 5) {
1422
- throw new Error(`Azure Disk ${spec.name}: maxShares cannot exceed 5`);
1423
- }
1424
- }
1425
- validateAzureDiskSectorSize(spec) {
1426
- if (spec.logicalSectorSize === undefined)
1427
- return;
1428
- if (spec.logicalSectorSize !== 512 && spec.logicalSectorSize !== 4096) {
1429
- throw new Error(`Azure Disk ${spec.name}: logicalSectorSize must be either 512 or 4096 bytes`);
1430
- }
1431
- }
1432
- /**
1433
- * Format cloud resource tags for provider-specific string format.
1434
- * Provides basic validation and consistent formatting across cloud providers.
1435
- */
1436
- formatCloudResourceTags(tags) {
1437
- this.validateCloudResourceTags(tags);
1438
- return Object.entries(tags)
1439
- .map(([key, value]) => `${key}=${value}`)
1440
- .join(',');
1441
- }
1442
- /**
1443
- * Validate cloud resource tags with basic rules.
1444
- * Each cloud provider has specific validation, but these are common basics.
1445
- */
1446
- validateCloudResourceTags(tags) {
1447
- for (const [key, value] of Object.entries(tags)) {
1448
- if (!key || key.trim() === '') {
1449
- throw new Error('Tag key cannot be empty');
1450
- }
1451
- if (value === undefined || value === null) {
1452
- throw new Error(`Tag value for key '${key}' cannot be null or undefined`);
1453
- }
1454
- }
1455
- }
1456
- addNetworkPolicy(spec) {
1457
- // Validate policy types
1458
- this.validateNetworkPolicyTypes(spec);
1459
- // Validate CIDR blocks if present
1460
- this.validateNetworkPolicyCIDRs(spec);
1461
- const np = new ApiObject(this.chart, spec.name, {
1462
- apiVersion: Rutter.NETWORKING_API_VERSION,
1463
- kind: 'NetworkPolicy',
1464
- metadata: {
1465
- name: spec.name,
1466
- ...(spec.labels ? { labels: spec.labels } : {}),
1467
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1468
- },
1469
- spec: {
1470
- podSelector: spec.podSelector ?? {},
1471
- policyTypes: spec.policyTypes,
1472
- ...(spec.ingress ? { ingress: spec.ingress } : {}),
1473
- ...(spec.egress ? { egress: spec.egress } : {}),
1474
- },
1475
- });
1476
- this.capture(np, `${spec.name}-networkpolicy`);
1477
- return np;
1478
- }
1479
- /**
1480
- * Create a deny-all ingress NetworkPolicy for the current namespace
1481
- */
1482
- addDenyAllIngressNetworkPolicy(name = 'deny-all-ingress') {
1483
- return this.addNetworkPolicy({
1484
- name,
1485
- podSelector: Rutter.EMPTY_POD_SELECTOR,
1486
- policyTypes: ['Ingress'],
1487
- });
1488
- }
1489
- /**
1490
- * Create a deny-all egress NetworkPolicy for the current namespace
1491
- */
1492
- addDenyAllEgressNetworkPolicy(name = 'deny-all-egress') {
1493
- return this.addNetworkPolicy({
1494
- name,
1495
- podSelector: Rutter.EMPTY_POD_SELECTOR,
1496
- policyTypes: ['Egress'],
1497
- });
1498
- }
1499
- /**
1500
- * Create a deny-all NetworkPolicy for both ingress and egress
1501
- */
1502
- addDenyAllNetworkPolicy(name = 'deny-all') {
1503
- return this.addNetworkPolicy({
1504
- name,
1505
- podSelector: Rutter.EMPTY_POD_SELECTOR,
1506
- policyTypes: ['Ingress', 'Egress'],
1507
- });
1508
- }
1509
- /**
1510
- * Allow traffic from specific pods to target pods on specific ports
1511
- */
1512
- addAllowFromPodsNetworkPolicy(spec) {
1513
- return this.addNetworkPolicy({
1514
- name: spec.name,
1515
- podSelector: { matchLabels: spec.targetPodSelector },
1516
- policyTypes: ['Ingress'],
1517
- ingress: [
1518
- {
1519
- from: [{ podSelector: { matchLabels: spec.sourcePodSelector } }],
1520
- ...(spec.ports ? { ports: spec.ports } : {}),
1521
- },
1522
- ],
1523
- ...(spec.labels ? { labels: spec.labels } : {}),
1524
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1525
- });
1526
- }
1527
- /**
1528
- * Allow traffic from specific namespace to target pods
1529
- */
1530
- addAllowFromNamespaceNetworkPolicy(spec) {
1531
- return this.addNetworkPolicy({
1532
- name: spec.name,
1533
- podSelector: { matchLabels: spec.targetPodSelector },
1534
- policyTypes: ['Ingress'],
1535
- ingress: [
1536
- {
1537
- from: [{ namespaceSelector: { matchLabels: spec.sourceNamespaceSelector } }],
1538
- ...(spec.ports ? { ports: spec.ports } : {}),
1539
- },
1540
- ],
1541
- ...(spec.labels ? { labels: spec.labels } : {}),
1542
- ...(spec.annotations ? { annotations: spec.annotations } : {}),
1543
- });
1544
- }
1545
- validateNetworkPolicyTypes(spec) {
1546
- if (!spec.policyTypes || spec.policyTypes.length === 0) {
1547
- throw new Error(`NetworkPolicy ${spec.name}: policyTypes must be specified`);
1548
- }
1549
- this.validatePolicyTypeValues(spec);
1550
- this.validatePolicyTypeRules(spec);
1551
- }
1552
- validatePolicyTypeValues(spec) {
1553
- const validTypes = ['Ingress', 'Egress'];
1554
- for (const type of spec.policyTypes) {
1555
- if (!validTypes.includes(type)) {
1556
- throw new Error(`NetworkPolicy ${spec.name}: invalid policyType '${type}', must be 'Ingress' or 'Egress'`);
1557
- }
1558
- }
1559
- }
1560
- validatePolicyTypeRules(spec) {
1561
- if (spec.policyTypes.includes('Ingress') && spec.ingress === undefined) {
1562
- console.warn(`NetworkPolicy ${spec.name}: policyType 'Ingress' specified but no ingress rules defined (will deny all ingress)`);
1563
- }
1564
- if (spec.policyTypes.includes('Egress') && spec.egress === undefined) {
1565
- console.warn(`NetworkPolicy ${spec.name}: policyType 'Egress' specified but no egress rules defined (will deny all egress)`);
1566
- }
1567
- }
1568
- validateNetworkPolicyCIDRs(spec) {
1569
- const validateCIDR = (cidr, context) => {
1570
- if (!this.isValidCIDR(cidr)) {
1571
- throw new Error(`NetworkPolicy ${spec.name}: invalid CIDR '${cidr}' in ${context}`);
1572
- }
1573
- };
1574
- this.validateIngressCIDRs(spec, validateCIDR);
1575
- this.validateEgressCIDRs(spec, validateCIDR);
1576
- }
1577
- isValidCIDR(cidr) {
1578
- // IPv4 CIDR validation
1579
- const ipv4Parts = cidr.split('/');
1580
- if (ipv4Parts.length === 2) {
1581
- const [ip, prefix] = ipv4Parts;
1582
- if (ip && prefix && this.isValidIPv4(ip) && this.isValidPrefix(prefix, 32)) {
1583
- return true;
1584
- }
1585
- }
1586
- // IPv6 CIDR validation (basic)
1587
- if (cidr.includes(':') && ipv4Parts.length === 2) {
1588
- const [, prefix] = ipv4Parts;
1589
- if (prefix) {
1590
- return this.isValidPrefix(prefix, 128);
1591
- }
1592
- }
1593
- return false;
1594
- }
1595
- isValidIPv4(ip) {
1596
- const parts = ip.split('.');
1597
- if (parts.length !== 4)
1598
- return false;
1599
- return parts.every((part) => {
1600
- const num = parseInt(part, 10);
1601
- return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
1602
- });
1603
- }
1604
- isValidPrefix(prefix, maxPrefix) {
1605
- const num = parseInt(prefix, 10);
1606
- return !isNaN(num) && num >= 0 && num <= maxPrefix && String(num) === prefix;
1607
- }
1608
- validateIngressCIDRs(spec, validateCIDR) {
1609
- if (!spec.ingress)
1610
- return;
1611
- for (const rule of spec.ingress) {
1612
- if (rule.from) {
1613
- for (const peer of rule.from) {
1614
- this.validatePeerCIDRs(peer, validateCIDR, 'ingress rule');
1615
- }
1616
- }
1617
- }
1618
- }
1619
- validateEgressCIDRs(spec, validateCIDR) {
1620
- if (!spec.egress)
1621
- return;
1622
- for (const rule of spec.egress) {
1623
- if (rule.to) {
1624
- for (const peer of rule.to) {
1625
- this.validatePeerCIDRs(peer, validateCIDR, 'egress rule');
1626
- }
1627
- }
1628
- }
1629
- }
1630
- validatePeerCIDRs(peer, validateCIDR, context) {
1631
- if (peer.ipBlock?.cidr) {
1632
- validateCIDR(peer.ipBlock.cidr, context);
1633
- if (peer.ipBlock.except) {
1634
- for (const except of peer.ipBlock.except) {
1635
- validateCIDR(except, `${context} except block`);
1636
- }
1637
- }
1638
- }
1639
- }
1640
- validateAGICHealthProbeParams(spec) {
1641
- if (spec.healthProbeInterval !== undefined &&
1642
- (spec.healthProbeInterval < 1 || spec.healthProbeInterval > 86400)) {
1643
- throw new Error('Health probe interval must be between 1 and 86400 seconds');
1644
- }
1645
- if (spec.healthProbeTimeout !== undefined &&
1646
- (spec.healthProbeTimeout < 1 || spec.healthProbeTimeout > 86400)) {
1647
- throw new Error('Health probe timeout must be between 1 and 86400 seconds');
1648
- }
1649
- if (spec.healthProbeUnhealthyThreshold !== undefined &&
1650
- (spec.healthProbeUnhealthyThreshold < 1 || spec.healthProbeUnhealthyThreshold > 20)) {
1651
- throw new Error('Health probe unhealthy threshold must be between 1 and 20');
1652
- }
1653
- }
1654
- validateAGICSecurityParams(spec) {
1655
- if (spec.wafPolicyForPath &&
1656
- !/^\/subscriptions\/[^/]+\/resourceGroups\/[^/]+\/providers\/Microsoft\.Network\/applicationGatewayWebApplicationFirewallPolicies\/[^/]+$/.test(spec.wafPolicyForPath)) {
1657
- throw new Error('Invalid WAF policy resource ID format');
1658
- }
1659
- if (spec.hostnameExtension?.some((hostname) => !this.isValidHostname(hostname))) {
1660
- throw new Error('Invalid hostname format in hostnameExtension');
1661
- }
1662
- }
1663
- isValidHostname(hostname) {
1664
- // Basic hostname validation without complex regex to prevent ReDoS
1665
- if (!hostname || hostname.length > 253)
1666
- return false;
1667
- if (hostname.startsWith('.') || hostname.endsWith('.'))
1668
- return false;
1669
- const labels = hostname.split('.');
1670
- return labels.every((label) => {
1671
- if (!label || label.length > 63)
1672
- return false;
1673
- if (label.startsWith('-') || label.endsWith('-'))
1674
- return false;
1675
- return /^[a-zA-Z0-9-]+$/.test(label);
1676
- });
1677
- }
1678
- validateAGICAnnotationConsistency(spec) {
1679
- if (spec.sslRedirect && !spec.appgwSslCertificate) {
1680
- throw new Error('SSL redirect requires an SSL certificate to be specified');
1681
- }
1682
- if (spec.backendProtocol === 'https' && !spec.appgwTrustedRootCertificate?.length) {
1683
- throw new Error('HTTPS backend protocol requires trusted root certificates');
1684
- }
1685
- if (spec.overrideFrontendPort === 443 && !spec.appgwSslCertificate) {
1686
- throw new Error('Port 443 requires an SSL certificate to be specified');
1687
- }
1688
- }
1689
- buildAzureFilesParameters(spec) {
1690
- const parameters = {
1691
- skuName: spec.skuName ?? 'Standard_LRS',
1692
- };
1693
- if (spec.protocol) {
1694
- parameters['protocol'] = spec.protocol;
1695
- }
1696
- if (spec.allowSharedAccess !== undefined) {
1697
- parameters['allowSharedAccess'] = String(spec.allowSharedAccess);
1698
- }
1699
- if (spec.resourceGroup) {
1700
- parameters['resourceGroup'] = spec.resourceGroup;
1701
- }
1702
- if (spec.storageAccount) {
1703
- parameters['storageAccount'] = spec.storageAccount;
1704
- }
1705
- if (spec.location) {
1706
- parameters['location'] = spec.location;
1707
- }
1708
- if (spec.networkEndpointType) {
1709
- parameters['networkEndpointType'] = spec.networkEndpointType;
1710
- }
1711
- // NFS-specific parameters
1712
- if (spec.protocol === 'nfs' && spec.mountPermissions) {
1713
- parameters['mountPermissions'] = spec.mountPermissions;
1714
- }
1715
- if (spec.protocol === 'nfs' && spec.rootSquashType) {
1716
- parameters['rootSquashType'] = spec.rootSquashType;
1717
- }
1718
- return parameters;
1719
- }
1720
- buildAzureFilesPVCSISpec(spec) {
1721
- // Validate protocol and secret configuration
1722
- if (spec.protocol === 'nfs' && spec.secretName) {
1723
- throw new Error(`Azure Files PV ${spec.name}: NFS protocol does not use secrets for authentication`);
1724
- }
1725
- const volumeHandle = this.sanitizeVolumeHandle(spec.storageAccount, spec.shareName);
1726
- const volumeAttributes = {
1727
- storageAccount: spec.storageAccount,
1728
- shareName: spec.shareName,
1729
- };
1730
- if (spec.resourceGroup) {
1731
- volumeAttributes['resourceGroup'] = spec.resourceGroup;
1732
- }
1733
- if (spec.protocol) {
1734
- volumeAttributes['protocol'] = spec.protocol;
1735
- }
1736
- if (spec.server) {
1737
- volumeAttributes['server'] = spec.server;
1738
- }
1739
- if (spec.folderName) {
1740
- volumeAttributes['folderName'] = spec.folderName;
1741
- }
1742
- if (spec.mountPermissions) {
1743
- volumeAttributes['mountPermissions'] = spec.mountPermissions;
1744
- }
1745
- const csiSpec = {
1746
- driver: Rutter.AZURE_FILES_CSI_DRIVER,
1747
- volumeHandle,
1748
- volumeAttributes,
1749
- };
1750
- // Add secret reference for SMB protocol
1751
- if (spec.protocol !== 'nfs' && spec.secretName) {
1752
- csiSpec['nodeStageSecretRef'] = {
1753
- name: spec.secretName,
1754
- namespace: spec.secretNamespace ?? 'default',
1755
- };
1756
- }
1757
- return csiSpec;
1758
- }
1759
- buildPodEnvironment(spec) {
1760
- const envEntries = spec.env
1761
- ? Object.entries(spec.env).map(([name, value]) => this.sanitizeEnvVar(name, value))
1762
- : undefined;
1763
- const envFromEntries = spec.envFrom
1764
- ? spec.envFrom
1765
- .map((s) => {
1766
- const entry = {};
1767
- if (s.configMapRef) {
1768
- entry.configMapRef = { name: s.configMapRef };
1769
- }
1770
- if (s.secretRef) {
1771
- entry.secretRef = { name: s.secretRef };
1772
- }
1773
- return entry;
1774
- })
1775
- .filter((entry) => entry.configMapRef || entry.secretRef)
1776
- : undefined;
1777
- const volumeDefs = spec.volumes
1778
- ? spec.volumes.map((v) => ({
1779
- name: v.name,
1780
- configMap: v.configMap ? { name: v.configMap } : undefined,
1781
- secret: v.secret ? { secretName: v.secret } : undefined,
1782
- persistentVolumeClaim: v.persistentVolumeClaim
1783
- ? { claimName: v.persistentVolumeClaim }
1784
- : undefined,
1785
- }))
1786
- : undefined;
1787
- const volumeMountDefs = spec.volumeMounts
1788
- ? spec.volumeMounts.map((m) => ({
1789
- name: m.name,
1790
- mountPath: m.mountPath,
1791
- readOnly: m.readOnly,
1792
- subPath: m.subPath,
1793
- }))
1794
- : undefined;
1795
- return { envEntries, envFromEntries, volumeDefs, volumeMountDefs };
1796
- }
1797
- buildJobSpec(spec) {
1798
- return {
1799
- ...(spec.backoffLimit !== undefined ? { backoffLimit: spec.backoffLimit } : {}),
1800
- ...(spec.activeDeadlineSeconds !== undefined
1801
- ? { activeDeadlineSeconds: spec.activeDeadlineSeconds }
1802
- : {}),
1803
- ...(spec.ttlSecondsAfterFinished !== undefined
1804
- ? { ttlSecondsAfterFinished: spec.ttlSecondsAfterFinished }
1805
- : {}),
1806
- ...(spec.completions !== undefined ? { completions: spec.completions } : {}),
1807
- ...(spec.parallelism !== undefined ? { parallelism: spec.parallelism } : {}),
1808
- ...(spec.completionMode !== undefined ? { completionMode: spec.completionMode } : {}),
1809
- ...(spec.suspend !== undefined ? { suspend: spec.suspend } : {}),
1810
- };
1811
- }
1812
- buildJobPodTemplate(spec, envEntries, envFromEntries, volumeDefs, volumeMountDefs) {
1813
- return {
1814
- metadata: {
1815
- ...(spec.podLabels ? { labels: spec.podLabels } : {}),
1816
- ...(spec.podAnnotations ? { annotations: spec.podAnnotations } : {}),
1817
- },
1818
- spec: {
1819
- restartPolicy: spec.restartPolicy ?? 'Never',
1820
- serviceAccountName: spec.serviceAccountName,
1821
- volumes: volumeDefs,
1822
- containers: [
1823
- {
1824
- name: spec.name,
1825
- image: spec.image,
1826
- imagePullPolicy: spec.imagePullPolicy,
1827
- ...(spec.command ? { command: spec.command } : {}),
1828
- ...(spec.args ? { args: spec.args } : {}),
1829
- env: envEntries,
1830
- envFrom: envFromEntries,
1831
- volumeMounts: volumeMountDefs,
1832
- resources: spec.resources ?? this.getDefaultResources(),
1833
- },
1834
- ],
1835
- },
1836
- };
1837
- }
1838
- buildCronJobSpec(spec) {
1839
- return {
1840
- schedule: spec.schedule,
1841
- ...(spec.startingDeadlineSeconds !== undefined
1842
- ? { startingDeadlineSeconds: spec.startingDeadlineSeconds }
1843
- : {}),
1844
- ...(spec.concurrencyPolicy !== undefined
1845
- ? { concurrencyPolicy: spec.concurrencyPolicy }
1846
- : {}),
1847
- ...(spec.suspend !== undefined ? { suspend: spec.suspend } : {}),
1848
- ...(spec.successfulJobsHistoryLimit !== undefined
1849
- ? { successfulJobsHistoryLimit: spec.successfulJobsHistoryLimit }
1850
- : {}),
1851
- ...(spec.failedJobsHistoryLimit !== undefined
1852
- ? { failedJobsHistoryLimit: spec.failedJobsHistoryLimit }
1853
- : {}),
1854
- ...(spec.timeZone !== undefined ? { timeZone: spec.timeZone } : {}),
1855
- };
1856
- }
1857
- buildCronJobTemplate(spec, envEntries, envFromEntries, volumeDefs, volumeMountDefs) {
1858
- return {
1859
- spec: {
1860
- ...(spec.backoffLimit !== undefined ? { backoffLimit: spec.backoffLimit } : {}),
1861
- ...(spec.activeDeadlineSeconds !== undefined
1862
- ? { activeDeadlineSeconds: spec.activeDeadlineSeconds }
1863
- : {}),
1864
- ...(spec.ttlSecondsAfterFinished !== undefined
1865
- ? { ttlSecondsAfterFinished: spec.ttlSecondsAfterFinished }
1866
- : {}),
1867
- ...(spec.completions !== undefined ? { completions: spec.completions } : {}),
1868
- ...(spec.parallelism !== undefined ? { parallelism: spec.parallelism } : {}),
1869
- ...(spec.completionMode !== undefined ? { completionMode: spec.completionMode } : {}),
1870
- template: {
1871
- metadata: {
1872
- ...(spec.podLabels ? { labels: spec.podLabels } : {}),
1873
- ...(spec.podAnnotations ? { annotations: spec.podAnnotations } : {}),
1874
- },
1875
- spec: {
1876
- restartPolicy: spec.restartPolicy ?? 'Never',
1877
- serviceAccountName: spec.serviceAccountName,
1878
- volumes: volumeDefs,
1879
- containers: [
1880
- {
1881
- name: spec.name,
1882
- image: spec.image,
1883
- imagePullPolicy: spec.imagePullPolicy,
1884
- ...(spec.command ? { command: spec.command } : {}),
1885
- ...(spec.args ? { args: spec.args } : {}),
1886
- env: envEntries,
1887
- envFrom: envFromEntries,
1888
- volumeMounts: volumeMountDefs,
1889
- resources: spec.resources ?? this.getDefaultResources(),
1890
- },
1891
- ],
1892
- },
1893
- },
1894
- },
1895
- };
1896
- }
1897
- /**
1898
- * Validate Job specification for security and best practices
1899
- */
1900
- validateJobSpec(spec) {
1901
- if (spec.activeDeadlineSeconds !== undefined && spec.activeDeadlineSeconds <= 0) {
1902
- throw new Error('activeDeadlineSeconds must be a positive integer');
1903
- }
1904
- if (spec.ttlSecondsAfterFinished !== undefined && spec.ttlSecondsAfterFinished < 0) {
1905
- throw new Error('ttlSecondsAfterFinished must be a non-negative integer');
1906
- }
1907
- }
1908
- /**
1909
- * Validate CronJob specification for security and best practices
1910
- */
1911
- validateCronJobSpec(spec) {
1912
- this.validateCronSchedule(spec.schedule);
1913
- if (spec.suspend && spec.startingDeadlineSeconds !== undefined) {
1914
- throw new Error('startingDeadlineSeconds should not be set when job is suspended');
1915
- }
1916
- if (spec.successfulJobsHistoryLimit !== undefined && spec.successfulJobsHistoryLimit < 0) {
1917
- throw new Error('successfulJobsHistoryLimit must be non-negative');
1918
- }
1919
- if (spec.failedJobsHistoryLimit !== undefined && spec.failedJobsHistoryLimit < 0) {
1920
- throw new Error('failedJobsHistoryLimit must be non-negative');
1921
- }
1922
- if (spec.activeDeadlineSeconds !== undefined && spec.activeDeadlineSeconds <= 0) {
1923
- throw new Error('activeDeadlineSeconds must be a positive integer');
1924
- }
1925
- if (spec.ttlSecondsAfterFinished !== undefined && spec.ttlSecondsAfterFinished < 0) {
1926
- throw new Error('ttlSecondsAfterFinished must be a non-negative integer');
1927
- }
1928
- }
1929
- /**
1930
- * Validate cron schedule expression
1931
- */
1932
- validateCronSchedule(schedule) {
1933
- const cronRegex = /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))$/;
1934
- if (!cronRegex.test(schedule)) {
1935
- throw new Error('Invalid cron schedule expression');
1936
- }
1937
- }
1938
- /**
1939
- * Sanitize environment variable names and values
1940
- */
1941
- sanitizeEnvVar(name, value) {
1942
- const nameRegex = /^[A-Za-z_][A-Za-z0-9_]*$/;
1943
- if (!nameRegex.test(name)) {
1944
- throw new Error(`Invalid environment variable name: ${name}`);
1945
- }
1946
- // Remove control characters from value (ASCII 0-31 and 127)
1947
- const sanitizedValue = value.replace(/[\cA-\c_\x7F]/g, '');
1948
- return { name, value: sanitizedValue };
1949
- }
1950
- /**
1951
- * Get default resource limits for Jobs and CronJobs
1952
- */
1953
- getDefaultResources() {
1954
- return {
1955
- limits: {
1956
- cpu: '1',
1957
- memory: '512Mi',
1958
- },
1959
- requests: {
1960
- cpu: '100m',
1961
- memory: '128Mi',
1962
- },
1963
- };
1964
- }
1965
- /**
1966
- * Validate Azure Files parameters for security and compliance
1967
- */
1968
- validateAzureFilesParameters(spec) {
1969
- const validSkuNames = [
1970
- 'Standard_LRS',
1971
- 'Standard_GRS',
1972
- 'Standard_RAGRS',
1973
- 'Standard_ZRS',
1974
- 'Premium_LRS',
1975
- 'Premium_ZRS',
1976
- ];
1977
- if (spec.skuName && !validSkuNames.includes(spec.skuName)) {
1978
- throw new Error(`Invalid skuName: ${spec.skuName}. Must be one of: ${validSkuNames.join(', ')}`);
1979
- }
1980
- if (spec.protocol === 'nfs' && spec.mountPermissions) {
1981
- const permRegex = /^[0-7]{3,4}$/;
1982
- if (!permRegex.test(spec.mountPermissions)) {
1983
- throw new Error('mountPermissions must be a valid octal permission string (e.g., "0777")');
1984
- }
1985
- }
1986
- }
1987
- /**
1988
- * Sanitize volume handle to ensure valid format for CSI driver
1989
- */
1990
- sanitizeVolumeHandle(account, share) {
1991
- // Remove special characters and ensure valid format
1992
- const sanitizedAccount = account.replace(/[^a-z0-9]/gi, '').toLowerCase();
1993
- const sanitizedShare = share.replace(/[^a-z0-9-]/gi, '').toLowerCase();
1994
- return `${sanitizedAccount}#${sanitizedShare}`;
1995
- }
1996
- /**
1997
- * Capture the YAML of an ApiObject or Construct into assets.
1998
- */
1999
- capture(_obj, id) {
2000
- // Ensure the app can synth
2001
- // We defer synth until write(), but we can capture logical ids now
2002
- this.assets.push({ id, yaml: `# captured:${id}` });
2003
- }
2004
- /**
2005
- * Extract a meaningful name from a Kubernetes resource object
2006
- */
2007
- getResourceName(obj) {
2008
- if (obj && typeof obj === 'object') {
2009
- const resource = obj;
2010
- if (resource.kind && resource.metadata?.name) {
2011
- return `${resource.kind.toLowerCase()}-${resource.metadata.name}`;
2012
- }
2013
- if (resource.kind) {
2014
- return resource.kind.toLowerCase();
2015
- }
2016
- }
2017
- return 'resource';
2018
- }
2019
- /**
2020
- * Add a raw CRD manifest to the chart (written under crds/).
2021
- */
2022
- addCrd(yaml, id = 'crd') {
2023
- this.assets.push({ id, yaml, target: 'crds' });
2024
- }
2025
- /**
2026
- * Synthesize the cdk8s app and write a Helm chart to outDir.
2027
- */
2028
- write(outDir) {
2029
- // Use cdk8s Testing.synth to obtain manifest objects
2030
- const manifestObjs = Testing.synth(this.chart);
2031
- // Enforce common labels best-practice on all rendered objects
2032
- const enriched = manifestObjs.map((obj) => {
2033
- if (obj && typeof obj === 'object') {
2034
- const o = obj;
2035
- o.metadata = o.metadata ?? {};
2036
- o.metadata.labels = o.metadata.labels ?? {};
2037
- const labels = o.metadata.labels;
2038
- const defaults = {
2039
- 'helm.sh/chart': '{{ .Chart.Name }}-{{ .Chart.Version }}',
2040
- 'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
2041
- 'app.kubernetes.io/instance': helm.releaseName,
2042
- 'app.kubernetes.io/version': helm.chartVersion,
2043
- 'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
2044
- 'app.kubernetes.io/part-of': helm.chartName,
2045
- };
2046
- for (const [k, v] of Object.entries(defaults)) {
2047
- if (!(k in labels)) {
2048
- // eslint-disable-next-line security/detect-object-injection -- Safe: controlled label assignment
2049
- labels[k] = v;
2050
- }
2051
- }
2052
- }
2053
- return obj;
2054
- });
2055
- if (this.props.singleManifestFile) {
2056
- // Combine all resources into a single manifest file
2057
- const combinedYaml = enriched
2058
- .map((obj) => YAML.stringify(obj).trim())
2059
- .filter(Boolean)
2060
- .join('\n---\n');
2061
- this.assets.length = 0;
2062
- const manifestId = this.props.manifestName ?? 'manifests';
2063
- this.assets.push({ id: manifestId, yaml: combinedYaml, singleFile: true });
2064
- }
2065
- else {
2066
- // Create separate files for each resource (default behavior)
2067
- this.assets.length = 0;
2068
- enriched.forEach((obj, _index) => {
2069
- const yaml = YAML.stringify(obj).trim();
2070
- if (yaml) {
2071
- const resourceName = this.getResourceName(obj);
2072
- const manifestId = this.props.manifestName
2073
- ? `${this.props.manifestName}-${resourceName}`
2074
- : resourceName;
2075
- this.assets.push({ id: manifestId, yaml });
2076
- }
2077
- });
2078
- }
2079
- const options = {
2080
- outDir,
2081
- meta: this.props.meta,
2082
- defaultValues: this.props.defaultValues
2083
- ? this.applyOverrides(this.props.defaultValues)
2084
- : undefined,
2085
- envValues: this.props.envValues,
2086
- assets: this.assets,
2087
- ...(this.props.helpersTpl !== undefined
2088
- ? { helpersTpl: this.props.helpersTpl }
2089
- : {
2090
- helpersTpl: [
2091
- {
2092
- name: 'timonel.name',
2093
- body: '{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}',
2094
- },
2095
- {
2096
- name: 'timonel.fullname',
2097
- body: '{{- if .Values.fullnameOverride -}}\n' +
2098
- '{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}\n' +
2099
- '{{- else -}}\n' +
2100
- '{{- printf "%s-%s" .Chart.Name .Release.Name | trunc 63 | trimSuffix "-" -}}\n' +
2101
- '{{- end -}}',
2102
- },
2103
- {
2104
- name: 'timonel.chart',
2105
- body: '{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}',
2106
- },
2107
- {
2108
- name: 'timonel.labels',
2109
- body: 'helm.sh/chart: {{ include "timonel.chart" . }}\n' +
2110
- 'app.kubernetes.io/name: {{ include "timonel.name" . }}\n' +
2111
- 'app.kubernetes.io/instance: {{ .Release.Name }}\n' +
2112
- 'app.kubernetes.io/version: {{ .Chart.Version }}\n' +
2113
- 'app.kubernetes.io/managed-by: {{ .Release.Service }}\n' +
2114
- 'app.kubernetes.io/part-of: {{ .Chart.Name }}',
2115
- },
2116
- {
2117
- name: 'timonel.selectorLabels',
2118
- body: 'app.kubernetes.io/name: {{ include "timonel.name" . }}\n' +
2119
- 'app.kubernetes.io/instance: {{ .Release.Name }}',
2120
- },
2121
- ],
2122
- }),
2123
- ...(this.props.notesTpl !== undefined ? { notesTpl: this.props.notesTpl } : {}),
2124
- ...(this.props.valuesSchema !== undefined ? { valuesSchema: this.props.valuesSchema } : {}),
2125
- };
2126
- HelmChartWriter.write(options);
2127
- }
2128
- }
2129
- Rutter.LABEL_NAME = 'app.kubernetes.io/name';
2130
- Rutter.LABEL_INSTANCE = 'app.kubernetes.io/instance';
2131
- Rutter.HELPER_NAME = 'timonel.name';
2132
- Rutter.NETWORKING_API_VERSION = 'networking.k8s.io/v1';
2133
- Rutter.STORAGE_API_VERSION = 'storage.k8s.io/v1';
2134
- Rutter.AZURE_DISK_CSI_DRIVER = 'disk.csi.azure.com';
2135
- Rutter.AZURE_DISK_PROVISIONER_ANNOTATION = 'volume.beta.kubernetes.io/storage-provisioner';
2136
- Rutter.AZURE_FILES_CSI_DRIVER = 'file.csi.azure.com';
2137
- Rutter.EMPTY_POD_SELECTOR = {};
2138
- //# sourceMappingURL=Rutter.js.map