timonel 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +96 -1
- package/README.md +432 -11
- package/dist/cli.js +32 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -20
- package/dist/index.js.map +1 -1
- package/dist/lib/HelmChartWriter.d.ts +2 -0
- package/dist/lib/HelmChartWriter.d.ts.map +1 -1
- package/dist/lib/HelmChartWriter.js +24 -52
- package/dist/lib/HelmChartWriter.js.map +1 -1
- package/dist/lib/Rutter.d.ts +396 -12
- package/dist/lib/Rutter.d.ts.map +1 -1
- package/dist/lib/Rutter.js +656 -56
- package/dist/lib/Rutter.js.map +1 -1
- package/dist/lib/UmbrellaRutter.d.ts +2 -2
- package/dist/lib/UmbrellaRutter.d.ts.map +1 -1
- package/dist/lib/UmbrellaRutter.js +12 -20
- package/dist/lib/UmbrellaRutter.js.map +1 -1
- package/dist/lib/helm.js +11 -24
- package/dist/lib/helm.js.map +1 -1
- package/dist/lib/umbrella.d.ts +2 -2
- package/dist/lib/umbrella.d.ts.map +1 -1
- package/dist/lib/umbrella.js +3 -6
- package/dist/lib/umbrella.js.map +1 -1
- package/package.json +16 -8
package/dist/lib/Rutter.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Rutter = void 0;
|
|
7
|
-
const cdk8s_1 = require("cdk8s");
|
|
8
|
-
const yaml_1 = __importDefault(require("yaml"));
|
|
9
|
-
const helm_1 = require("./helm");
|
|
10
|
-
const HelmChartWriter_1 = require("./HelmChartWriter");
|
|
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';
|
|
11
5
|
/**
|
|
12
6
|
* Rutter builds Kubernetes manifests using cdk8s and writes a Helm chart.
|
|
13
7
|
*/
|
|
14
|
-
class Rutter {
|
|
8
|
+
export class Rutter {
|
|
15
9
|
constructor(props) {
|
|
16
10
|
this.assets = [];
|
|
17
11
|
this.valueOverrides = {};
|
|
18
12
|
this.props = props;
|
|
19
|
-
this.app = new
|
|
20
|
-
this.chart = new
|
|
13
|
+
this.app = new App();
|
|
14
|
+
this.chart = new Chart(this.app, props.manifestName ?? props.meta.name);
|
|
21
15
|
}
|
|
22
16
|
/**
|
|
23
17
|
* Set dynamic value overrides (from --set flags)
|
|
@@ -68,8 +62,8 @@ class Rutter {
|
|
|
68
62
|
}
|
|
69
63
|
addDeployment(spec) {
|
|
70
64
|
const match = spec.matchLabels ?? {
|
|
71
|
-
[Rutter.LABEL_NAME]:
|
|
72
|
-
[Rutter.LABEL_INSTANCE]:
|
|
65
|
+
[Rutter.LABEL_NAME]: include(Rutter.HELPER_NAME),
|
|
66
|
+
[Rutter.LABEL_INSTANCE]: helm.releaseName,
|
|
73
67
|
};
|
|
74
68
|
const envEntries = spec.env
|
|
75
69
|
? Object.entries(spec.env).map(([name, value]) => ({ name, value }))
|
|
@@ -99,7 +93,7 @@ class Rutter {
|
|
|
99
93
|
}))
|
|
100
94
|
: undefined;
|
|
101
95
|
const templateLabels = { ...match, ...(spec.podLabels ?? {}) };
|
|
102
|
-
const dep = new
|
|
96
|
+
const dep = new ApiObject(this.chart, spec.name, {
|
|
103
97
|
apiVersion: 'apps/v1',
|
|
104
98
|
kind: 'Deployment',
|
|
105
99
|
metadata: {
|
|
@@ -140,7 +134,7 @@ class Rutter {
|
|
|
140
134
|
return dep;
|
|
141
135
|
}
|
|
142
136
|
addService(spec) {
|
|
143
|
-
const svc = new
|
|
137
|
+
const svc = new ApiObject(this.chart, spec.name, {
|
|
144
138
|
apiVersion: 'v1',
|
|
145
139
|
kind: 'Service',
|
|
146
140
|
metadata: {
|
|
@@ -175,8 +169,8 @@ class Rutter {
|
|
|
175
169
|
}
|
|
176
170
|
addReplicaSet(spec) {
|
|
177
171
|
const match = spec.matchLabels ?? {
|
|
178
|
-
[Rutter.LABEL_NAME]:
|
|
179
|
-
[Rutter.LABEL_INSTANCE]:
|
|
172
|
+
[Rutter.LABEL_NAME]: include(Rutter.HELPER_NAME),
|
|
173
|
+
[Rutter.LABEL_INSTANCE]: helm.releaseName,
|
|
180
174
|
};
|
|
181
175
|
const envEntries = spec.env
|
|
182
176
|
? Object.entries(spec.env).map(([name, value]) => ({ name, value }))
|
|
@@ -206,7 +200,7 @@ class Rutter {
|
|
|
206
200
|
}))
|
|
207
201
|
: undefined;
|
|
208
202
|
const templateLabels = { ...match, ...(spec.podLabels ?? {}) };
|
|
209
|
-
const rs = new
|
|
203
|
+
const rs = new ApiObject(this.chart, spec.name, {
|
|
210
204
|
apiVersion: 'apps/v1',
|
|
211
205
|
kind: 'ReplicaSet',
|
|
212
206
|
metadata: {
|
|
@@ -247,8 +241,8 @@ class Rutter {
|
|
|
247
241
|
return rs;
|
|
248
242
|
}
|
|
249
243
|
addIngress(spec) {
|
|
250
|
-
const ing = new
|
|
251
|
-
apiVersion:
|
|
244
|
+
const ing = new ApiObject(this.chart, spec.name, {
|
|
245
|
+
apiVersion: Rutter.NETWORKING_API_VERSION,
|
|
252
246
|
kind: 'Ingress',
|
|
253
247
|
metadata: {
|
|
254
248
|
name: spec.name,
|
|
@@ -321,7 +315,7 @@ class Rutter {
|
|
|
321
315
|
pvSpec[key] = value;
|
|
322
316
|
}
|
|
323
317
|
}
|
|
324
|
-
const pv = new
|
|
318
|
+
const pv = new ApiObject(this.chart, spec.name, {
|
|
325
319
|
apiVersion: 'v1',
|
|
326
320
|
kind: 'PersistentVolume',
|
|
327
321
|
metadata: {
|
|
@@ -336,14 +330,15 @@ class Rutter {
|
|
|
336
330
|
}
|
|
337
331
|
optimizePVForCloud(spec) {
|
|
338
332
|
const optimized = { ...spec };
|
|
333
|
+
const EBS_CSI_DRIVER = 'ebs.csi.aws.com';
|
|
339
334
|
// Apply cloud-specific optimizations
|
|
340
335
|
if (spec.cloudProvider === 'aws') {
|
|
341
336
|
optimized.annotations = {
|
|
342
337
|
...optimized.annotations,
|
|
343
|
-
'volume.beta.kubernetes.io/storage-provisioner':
|
|
338
|
+
'volume.beta.kubernetes.io/storage-provisioner': EBS_CSI_DRIVER,
|
|
344
339
|
};
|
|
345
340
|
// Default to gp3 for better performance/cost
|
|
346
|
-
if (spec.source.csi?.driver ===
|
|
341
|
+
if (spec.source.csi?.driver === EBS_CSI_DRIVER && optimized.source.csi) {
|
|
347
342
|
optimized.source.csi.volumeAttributes = {
|
|
348
343
|
type: 'gp3',
|
|
349
344
|
...spec.source.csi.volumeAttributes,
|
|
@@ -377,7 +372,7 @@ class Rutter {
|
|
|
377
372
|
addPersistentVolumeClaim(spec) {
|
|
378
373
|
// Validate access modes compatibility
|
|
379
374
|
this.validatePVCAccessModes(spec);
|
|
380
|
-
const pvc = new
|
|
375
|
+
const pvc = new ApiObject(this.chart, spec.name, {
|
|
381
376
|
apiVersion: 'v1',
|
|
382
377
|
kind: 'PersistentVolumeClaim',
|
|
383
378
|
metadata: {
|
|
@@ -412,7 +407,7 @@ class Rutter {
|
|
|
412
407
|
}
|
|
413
408
|
}
|
|
414
409
|
addConfigMap(spec) {
|
|
415
|
-
const cm = new
|
|
410
|
+
const cm = new ApiObject(this.chart, spec.name, {
|
|
416
411
|
apiVersion: 'v1',
|
|
417
412
|
kind: 'ConfigMap',
|
|
418
413
|
metadata: {
|
|
@@ -428,7 +423,7 @@ class Rutter {
|
|
|
428
423
|
return cm;
|
|
429
424
|
}
|
|
430
425
|
addSecret(spec) {
|
|
431
|
-
const sec = new
|
|
426
|
+
const sec = new ApiObject(this.chart, spec.name, {
|
|
432
427
|
apiVersion: 'v1',
|
|
433
428
|
kind: 'Secret',
|
|
434
429
|
metadata: {
|
|
@@ -445,27 +440,378 @@ class Rutter {
|
|
|
445
440
|
return sec;
|
|
446
441
|
}
|
|
447
442
|
addServiceAccount(spec) {
|
|
448
|
-
const
|
|
443
|
+
const annotations = this.buildServiceAccountAnnotations(spec);
|
|
444
|
+
const sa = new ApiObject(this.chart, spec.name, {
|
|
445
|
+
apiVersion: 'v1',
|
|
446
|
+
kind: 'ServiceAccount',
|
|
447
|
+
metadata: {
|
|
448
|
+
name: spec.name,
|
|
449
|
+
...(Object.keys(annotations).length ? { annotations } : {}),
|
|
450
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
451
|
+
},
|
|
452
|
+
automountServiceAccountToken: spec.automountServiceAccountToken,
|
|
453
|
+
imagePullSecrets: spec.imagePullSecrets?.map((n) => ({ name: n })),
|
|
454
|
+
secrets: spec.secrets?.map((n) => ({ name: n })),
|
|
455
|
+
});
|
|
456
|
+
this.capture(sa, `${spec.name}-serviceaccount`);
|
|
457
|
+
return sa;
|
|
458
|
+
}
|
|
459
|
+
buildServiceAccountAnnotations(spec) {
|
|
460
|
+
const annotations = {
|
|
449
461
|
...(spec.annotations ?? {}),
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
462
|
+
};
|
|
463
|
+
this.addAWSIRSAAnnotations(annotations, spec);
|
|
464
|
+
this.addAzureWorkloadIdentityAnnotations(annotations, spec);
|
|
465
|
+
this.addGCPWorkloadIdentityAnnotations(annotations, spec);
|
|
466
|
+
return annotations;
|
|
467
|
+
}
|
|
468
|
+
addAWSIRSAAnnotations(annotations, spec) {
|
|
469
|
+
if (spec.awsRoleArn) {
|
|
470
|
+
annotations['eks.amazonaws.com/role-arn'] = spec.awsRoleArn;
|
|
471
|
+
}
|
|
472
|
+
if (spec.awsAudience) {
|
|
473
|
+
annotations['eks.amazonaws.com/audience'] = spec.awsAudience;
|
|
474
|
+
}
|
|
475
|
+
if (spec.awsStsEndpointType) {
|
|
476
|
+
annotations['eks.amazonaws.com/sts-regional-endpoints'] = spec.awsStsEndpointType;
|
|
477
|
+
}
|
|
478
|
+
if (spec.awsTokenExpiration !== undefined) {
|
|
479
|
+
annotations['eks.amazonaws.com/token-expiration'] = String(spec.awsTokenExpiration);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
addAzureWorkloadIdentityAnnotations(annotations, spec) {
|
|
483
|
+
if (spec.azureClientId) {
|
|
484
|
+
annotations['azure.workload.identity/client-id'] = spec.azureClientId;
|
|
485
|
+
}
|
|
486
|
+
if (spec.azureTenantId) {
|
|
487
|
+
annotations['azure.workload.identity/tenant-id'] = spec.azureTenantId;
|
|
488
|
+
}
|
|
489
|
+
if (spec.azureServiceAccountTokenExpiration !== undefined) {
|
|
490
|
+
annotations['azure.workload.identity/service-account-token-expiration'] = String(spec.azureServiceAccountTokenExpiration);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
addGCPWorkloadIdentityAnnotations(annotations, spec) {
|
|
494
|
+
if (spec.gcpServiceAccountEmail) {
|
|
495
|
+
annotations['iam.gke.io/gcp-service-account'] = spec.gcpServiceAccountEmail;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
addHorizontalPodAutoscaler(spec) {
|
|
499
|
+
// Default metrics if none provided (CPU utilization at 80%)
|
|
500
|
+
const defaultMetrics = [
|
|
501
|
+
{
|
|
502
|
+
type: 'Resource',
|
|
503
|
+
resource: {
|
|
504
|
+
name: 'cpu',
|
|
505
|
+
target: {
|
|
506
|
+
type: 'Utilization',
|
|
507
|
+
averageUtilization: 80,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
];
|
|
512
|
+
const hpa = new ApiObject(this.chart, spec.name, {
|
|
513
|
+
apiVersion: 'autoscaling/v2',
|
|
514
|
+
kind: 'HorizontalPodAutoscaler',
|
|
515
|
+
metadata: {
|
|
516
|
+
name: spec.name,
|
|
517
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
518
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
519
|
+
},
|
|
520
|
+
spec: {
|
|
521
|
+
scaleTargetRef: spec.scaleTargetRef,
|
|
522
|
+
minReplicas: spec.minReplicas ?? 1,
|
|
523
|
+
maxReplicas: spec.maxReplicas,
|
|
524
|
+
metrics: spec.metrics ?? defaultMetrics,
|
|
525
|
+
...(spec.behavior ? { behavior: spec.behavior } : {}),
|
|
526
|
+
},
|
|
527
|
+
});
|
|
528
|
+
this.capture(hpa, `${spec.name}-hpa`);
|
|
529
|
+
return hpa;
|
|
530
|
+
}
|
|
531
|
+
addVerticalPodAutoscaler(spec) {
|
|
532
|
+
const vpa = new ApiObject(this.chart, spec.name, {
|
|
533
|
+
apiVersion: 'autoscaling.k8s.io/v1',
|
|
534
|
+
kind: 'VerticalPodAutoscaler',
|
|
535
|
+
metadata: {
|
|
536
|
+
name: spec.name,
|
|
537
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
538
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
539
|
+
},
|
|
540
|
+
spec: {
|
|
541
|
+
targetRef: spec.targetRef,
|
|
542
|
+
updatePolicy: spec.updatePolicy ?? { updateMode: 'Auto' },
|
|
543
|
+
...(spec.resourcePolicy ? { resourcePolicy: spec.resourcePolicy } : {}),
|
|
544
|
+
...(spec.recommenders ? { recommenders: spec.recommenders } : {}),
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
this.capture(vpa, `${spec.name}-vpa`);
|
|
548
|
+
return vpa;
|
|
549
|
+
}
|
|
550
|
+
addPodDisruptionBudget(spec) {
|
|
551
|
+
const pdb = new ApiObject(this.chart, spec.name, {
|
|
552
|
+
apiVersion: 'policy/v1',
|
|
553
|
+
kind: 'PodDisruptionBudget',
|
|
554
|
+
metadata: {
|
|
555
|
+
name: spec.name,
|
|
556
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
557
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
558
|
+
},
|
|
559
|
+
spec: {
|
|
560
|
+
...(spec.minAvailable !== undefined ? { minAvailable: spec.minAvailable } : {}),
|
|
561
|
+
...(spec.maxUnavailable !== undefined ? { maxUnavailable: spec.maxUnavailable } : {}),
|
|
562
|
+
selector: spec.selector,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
this.capture(pdb, `${spec.name}-pdb`);
|
|
566
|
+
return pdb;
|
|
567
|
+
}
|
|
568
|
+
addAWSEBSStorageClass(spec) {
|
|
569
|
+
const DEFAULT_VOLUME_TYPE = 'gp3';
|
|
570
|
+
const parameters = {
|
|
571
|
+
type: spec.volumeType ?? DEFAULT_VOLUME_TYPE,
|
|
572
|
+
fsType: spec.fsType ?? 'ext4',
|
|
573
|
+
encrypted: String(spec.encrypted ?? true),
|
|
574
|
+
};
|
|
575
|
+
if (spec.kmsKeyId) {
|
|
576
|
+
parameters['kmsKeyId'] = spec.kmsKeyId;
|
|
577
|
+
}
|
|
578
|
+
const IOPS_SUPPORTED_TYPES = ['gp3', 'io1', 'io2'];
|
|
579
|
+
if (spec.iops && IOPS_SUPPORTED_TYPES.includes(spec.volumeType ?? DEFAULT_VOLUME_TYPE)) {
|
|
580
|
+
parameters['iops'] = String(spec.iops);
|
|
581
|
+
}
|
|
582
|
+
if (spec.throughput && spec.volumeType === DEFAULT_VOLUME_TYPE) {
|
|
583
|
+
parameters['throughput'] = String(spec.throughput);
|
|
584
|
+
}
|
|
585
|
+
const sc = new ApiObject(this.chart, spec.name, {
|
|
586
|
+
apiVersion: 'storage.k8s.io/v1',
|
|
587
|
+
kind: 'StorageClass',
|
|
588
|
+
metadata: {
|
|
589
|
+
name: spec.name,
|
|
590
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
591
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
592
|
+
},
|
|
593
|
+
provisioner: 'ebs.csi.aws.com',
|
|
594
|
+
parameters,
|
|
595
|
+
reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
|
|
596
|
+
allowVolumeExpansion: spec.allowVolumeExpansion ?? true,
|
|
597
|
+
volumeBindingMode: spec.volumeBindingMode ?? 'WaitForFirstConsumer',
|
|
598
|
+
});
|
|
599
|
+
this.capture(sc, `${spec.name}-storageclass`);
|
|
600
|
+
return sc;
|
|
601
|
+
}
|
|
602
|
+
addAWSEBSPersistentVolumeClaim(spec) {
|
|
603
|
+
const pvc = new ApiObject(this.chart, spec.name, {
|
|
604
|
+
apiVersion: 'v1',
|
|
605
|
+
kind: 'PersistentVolumeClaim',
|
|
606
|
+
metadata: {
|
|
607
|
+
name: spec.name,
|
|
608
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
609
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
610
|
+
},
|
|
611
|
+
spec: {
|
|
612
|
+
accessModes: spec.accessModes ?? ['ReadWriteOnce'],
|
|
613
|
+
storageClassName: spec.storageClassName,
|
|
614
|
+
resources: {
|
|
615
|
+
requests: {
|
|
616
|
+
storage: spec.size,
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
this.capture(pvc, `${spec.name}-pvc`);
|
|
622
|
+
return pvc;
|
|
623
|
+
}
|
|
624
|
+
addAWSEFSStorageClass(spec) {
|
|
625
|
+
const sc = new ApiObject(this.chart, spec.name, {
|
|
626
|
+
apiVersion: 'storage.k8s.io/v1',
|
|
627
|
+
kind: 'StorageClass',
|
|
628
|
+
metadata: {
|
|
629
|
+
name: spec.name,
|
|
630
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
631
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
632
|
+
},
|
|
633
|
+
provisioner: 'efs.csi.aws.com',
|
|
634
|
+
reclaimPolicy: spec.reclaimPolicy ?? 'Delete',
|
|
635
|
+
volumeBindingMode: spec.volumeBindingMode ?? 'Immediate',
|
|
636
|
+
});
|
|
637
|
+
this.capture(sc, `${spec.name}-storageclass`);
|
|
638
|
+
return sc;
|
|
639
|
+
}
|
|
640
|
+
addAWSEFSPersistentVolume(spec) {
|
|
641
|
+
const csiSpec = {
|
|
642
|
+
driver: 'efs.csi.aws.com',
|
|
643
|
+
volumeHandle: spec.fileSystemId,
|
|
644
|
+
};
|
|
645
|
+
if (spec.accessPoint) {
|
|
646
|
+
csiSpec['volumeHandle'] = `${spec.fileSystemId}::${spec.accessPoint}`;
|
|
647
|
+
}
|
|
648
|
+
const volumeAttributes = {};
|
|
649
|
+
if (spec.directoryPerms) {
|
|
650
|
+
volumeAttributes['directoryPerms'] = spec.directoryPerms;
|
|
651
|
+
}
|
|
652
|
+
if (spec.gidRangeStart !== undefined) {
|
|
653
|
+
volumeAttributes['gidRangeStart'] = String(spec.gidRangeStart);
|
|
654
|
+
}
|
|
655
|
+
if (spec.gidRangeEnd !== undefined) {
|
|
656
|
+
volumeAttributes['gidRangeEnd'] = String(spec.gidRangeEnd);
|
|
657
|
+
}
|
|
658
|
+
if (spec.basePath) {
|
|
659
|
+
volumeAttributes['basePath'] = spec.basePath;
|
|
660
|
+
}
|
|
661
|
+
if (spec.subPath) {
|
|
662
|
+
volumeAttributes['subPath'] = spec.subPath;
|
|
663
|
+
}
|
|
664
|
+
if (Object.keys(volumeAttributes).length > 0) {
|
|
665
|
+
csiSpec['volumeAttributes'] = volumeAttributes;
|
|
666
|
+
}
|
|
667
|
+
const pv = new ApiObject(this.chart, spec.name, {
|
|
668
|
+
apiVersion: 'v1',
|
|
669
|
+
kind: 'PersistentVolume',
|
|
670
|
+
metadata: {
|
|
671
|
+
name: spec.name,
|
|
672
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
673
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
674
|
+
},
|
|
675
|
+
spec: {
|
|
676
|
+
capacity: {
|
|
677
|
+
storage: spec.capacity ?? '5Gi',
|
|
678
|
+
},
|
|
679
|
+
accessModes: spec.accessModes ?? ['ReadWriteMany'],
|
|
680
|
+
persistentVolumeReclaimPolicy: 'Retain',
|
|
681
|
+
storageClassName: spec.storageClassName,
|
|
682
|
+
csi: csiSpec,
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
this.capture(pv, `${spec.name}-pv`);
|
|
686
|
+
return pv;
|
|
687
|
+
}
|
|
688
|
+
addAWSEFSPersistentVolumeClaim(spec) {
|
|
689
|
+
const pvc = new ApiObject(this.chart, spec.name, {
|
|
690
|
+
apiVersion: 'v1',
|
|
691
|
+
kind: 'PersistentVolumeClaim',
|
|
692
|
+
metadata: {
|
|
693
|
+
name: spec.name,
|
|
694
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
695
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
696
|
+
},
|
|
697
|
+
spec: {
|
|
698
|
+
accessModes: spec.accessModes ?? ['ReadWriteMany'],
|
|
699
|
+
storageClassName: spec.storageClassName,
|
|
700
|
+
resources: {
|
|
701
|
+
requests: {
|
|
702
|
+
storage: spec.size ?? '5Gi',
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
this.capture(pvc, `${spec.name}-pvc`);
|
|
708
|
+
return pvc;
|
|
709
|
+
}
|
|
710
|
+
addAWSALBIngress(spec) {
|
|
711
|
+
const annotations = this.buildALBAnnotations(spec);
|
|
712
|
+
const ingress = new ApiObject(this.chart, spec.name, {
|
|
713
|
+
apiVersion: Rutter.NETWORKING_API_VERSION,
|
|
714
|
+
kind: 'Ingress',
|
|
715
|
+
metadata: {
|
|
716
|
+
name: spec.name,
|
|
717
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
718
|
+
annotations,
|
|
719
|
+
},
|
|
720
|
+
spec: {
|
|
721
|
+
tls: spec.tls,
|
|
722
|
+
rules: spec.rules.map((rule) => ({
|
|
723
|
+
host: rule.host,
|
|
724
|
+
http: {
|
|
725
|
+
paths: rule.paths.map((path) => ({
|
|
726
|
+
path: path.path,
|
|
727
|
+
pathType: path.pathType,
|
|
728
|
+
backend: path.backend,
|
|
729
|
+
})),
|
|
730
|
+
},
|
|
731
|
+
})),
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
this.capture(ingress, `${spec.name}-ingress`);
|
|
735
|
+
return ingress;
|
|
736
|
+
}
|
|
737
|
+
buildALBAnnotations(spec) {
|
|
738
|
+
const annotations = {
|
|
739
|
+
'kubernetes.io/ingress.class': 'alb',
|
|
740
|
+
'alb.ingress.kubernetes.io/scheme': spec.scheme ?? 'internet-facing',
|
|
741
|
+
'alb.ingress.kubernetes.io/target-type': spec.targetType ?? 'ip',
|
|
742
|
+
...(spec.annotations ?? {}),
|
|
743
|
+
};
|
|
744
|
+
this.addOptionalALBAnnotations(annotations, spec);
|
|
745
|
+
this.addHealthCheckAnnotations(annotations, spec);
|
|
746
|
+
this.addTagsAnnotation(annotations, spec);
|
|
747
|
+
return annotations;
|
|
748
|
+
}
|
|
749
|
+
addOptionalALBAnnotations(annotations, spec) {
|
|
750
|
+
if (spec.ipAddressType) {
|
|
751
|
+
annotations['alb.ingress.kubernetes.io/ip-address-type'] = spec.ipAddressType;
|
|
752
|
+
}
|
|
753
|
+
if (spec.groupName) {
|
|
754
|
+
annotations['alb.ingress.kubernetes.io/group.name'] = spec.groupName;
|
|
755
|
+
}
|
|
756
|
+
if (spec.groupOrder !== undefined) {
|
|
757
|
+
annotations['alb.ingress.kubernetes.io/group.order'] = String(spec.groupOrder);
|
|
758
|
+
}
|
|
759
|
+
if (spec.subnets && spec.subnets.length > 0) {
|
|
760
|
+
annotations['alb.ingress.kubernetes.io/subnets'] = spec.subnets.join(',');
|
|
761
|
+
}
|
|
762
|
+
if (spec.securityGroups && spec.securityGroups.length > 0) {
|
|
763
|
+
annotations['alb.ingress.kubernetes.io/security-groups'] = spec.securityGroups.join(',');
|
|
764
|
+
}
|
|
765
|
+
if (spec.certificateArn) {
|
|
766
|
+
annotations['alb.ingress.kubernetes.io/certificate-arn'] = spec.certificateArn;
|
|
767
|
+
}
|
|
768
|
+
if (spec.sslRedirect) {
|
|
769
|
+
annotations['alb.ingress.kubernetes.io/ssl-redirect'] = '443';
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
addHealthCheckAnnotations(annotations, spec) {
|
|
773
|
+
if (spec.healthCheckPath) {
|
|
774
|
+
annotations['alb.ingress.kubernetes.io/healthcheck-path'] = spec.healthCheckPath;
|
|
775
|
+
}
|
|
776
|
+
if (spec.healthCheckIntervalSeconds !== undefined) {
|
|
777
|
+
annotations['alb.ingress.kubernetes.io/healthcheck-interval-seconds'] = String(spec.healthCheckIntervalSeconds);
|
|
778
|
+
}
|
|
779
|
+
if (spec.healthCheckTimeoutSeconds !== undefined) {
|
|
780
|
+
annotations['alb.ingress.kubernetes.io/healthcheck-timeout-seconds'] = String(spec.healthCheckTimeoutSeconds);
|
|
781
|
+
}
|
|
782
|
+
if (spec.healthyThresholdCount !== undefined) {
|
|
783
|
+
annotations['alb.ingress.kubernetes.io/healthy-threshold-count'] = String(spec.healthyThresholdCount);
|
|
784
|
+
}
|
|
785
|
+
if (spec.unhealthyThresholdCount !== undefined) {
|
|
786
|
+
annotations['alb.ingress.kubernetes.io/unhealthy-threshold-count'] = String(spec.unhealthyThresholdCount);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
addTagsAnnotation(annotations, spec) {
|
|
790
|
+
if (spec.tags && Object.keys(spec.tags).length > 0) {
|
|
791
|
+
const tagString = Object.entries(spec.tags)
|
|
792
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
793
|
+
.join(',');
|
|
794
|
+
annotations['alb.ingress.kubernetes.io/tags'] = tagString;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
addAWSIRSAServiceAccount(spec) {
|
|
798
|
+
const annotations = {
|
|
799
|
+
'eks.amazonaws.com/role-arn': spec.roleArn,
|
|
800
|
+
...(spec.audience ? { 'eks.amazonaws.com/audience': spec.audience } : {}),
|
|
801
|
+
...(spec.stsEndpointType
|
|
802
|
+
? { 'eks.amazonaws.com/sts-regional-endpoints': spec.stsEndpointType }
|
|
803
|
+
: { 'eks.amazonaws.com/sts-regional-endpoints': 'regional' }),
|
|
804
|
+
...(spec.tokenExpiration !== undefined
|
|
805
|
+
? { 'eks.amazonaws.com/token-expiration': String(spec.tokenExpiration) }
|
|
461
806
|
: {}),
|
|
807
|
+
...(spec.annotations ?? {}),
|
|
462
808
|
};
|
|
463
|
-
const sa = new
|
|
809
|
+
const sa = new ApiObject(this.chart, spec.name, {
|
|
464
810
|
apiVersion: 'v1',
|
|
465
811
|
kind: 'ServiceAccount',
|
|
466
812
|
metadata: {
|
|
467
813
|
name: spec.name,
|
|
468
|
-
|
|
814
|
+
annotations,
|
|
469
815
|
...(spec.labels ? { labels: spec.labels } : {}),
|
|
470
816
|
},
|
|
471
817
|
automountServiceAccountToken: spec.automountServiceAccountToken,
|
|
@@ -475,6 +821,227 @@ class Rutter {
|
|
|
475
821
|
this.capture(sa, `${spec.name}-serviceaccount`);
|
|
476
822
|
return sa;
|
|
477
823
|
}
|
|
824
|
+
addAWSSecretProviderClass(spec) {
|
|
825
|
+
const objects = spec.objects.map((obj) => {
|
|
826
|
+
const baseObj = {
|
|
827
|
+
objectName: obj.objectName,
|
|
828
|
+
objectType: obj.objectType,
|
|
829
|
+
};
|
|
830
|
+
if ('objectAlias' in obj && obj.objectAlias) {
|
|
831
|
+
baseObj['objectAlias'] = obj.objectAlias;
|
|
832
|
+
}
|
|
833
|
+
if ('jmesPath' in obj && obj.jmesPath) {
|
|
834
|
+
baseObj['jmesPath'] = obj.jmesPath;
|
|
835
|
+
}
|
|
836
|
+
return baseObj;
|
|
837
|
+
});
|
|
838
|
+
const objectsYaml = YAML.stringify(objects, { indent: 2 }).trim();
|
|
839
|
+
const parameters = {
|
|
840
|
+
objects: `|\n ${objectsYaml.split('\n').join('\n ')}`,
|
|
841
|
+
};
|
|
842
|
+
if (spec.region) {
|
|
843
|
+
parameters['region'] = spec.region;
|
|
844
|
+
}
|
|
845
|
+
const spc = new ApiObject(this.chart, spec.name, {
|
|
846
|
+
apiVersion: 'secrets-store.csi.x-k8s.io/v1',
|
|
847
|
+
kind: 'SecretProviderClass',
|
|
848
|
+
metadata: {
|
|
849
|
+
name: spec.name,
|
|
850
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
851
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
852
|
+
},
|
|
853
|
+
spec: {
|
|
854
|
+
provider: 'aws',
|
|
855
|
+
parameters,
|
|
856
|
+
},
|
|
857
|
+
});
|
|
858
|
+
this.capture(spc, `${spec.name}-secretproviderclass`);
|
|
859
|
+
return spc;
|
|
860
|
+
}
|
|
861
|
+
addNetworkPolicy(spec) {
|
|
862
|
+
// Validate policy types
|
|
863
|
+
this.validateNetworkPolicyTypes(spec);
|
|
864
|
+
// Validate CIDR blocks if present
|
|
865
|
+
this.validateNetworkPolicyCIDRs(spec);
|
|
866
|
+
const np = new ApiObject(this.chart, spec.name, {
|
|
867
|
+
apiVersion: Rutter.NETWORKING_API_VERSION,
|
|
868
|
+
kind: 'NetworkPolicy',
|
|
869
|
+
metadata: {
|
|
870
|
+
name: spec.name,
|
|
871
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
872
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
873
|
+
},
|
|
874
|
+
spec: {
|
|
875
|
+
podSelector: spec.podSelector ?? {},
|
|
876
|
+
policyTypes: spec.policyTypes,
|
|
877
|
+
...(spec.ingress ? { ingress: spec.ingress } : {}),
|
|
878
|
+
...(spec.egress ? { egress: spec.egress } : {}),
|
|
879
|
+
},
|
|
880
|
+
});
|
|
881
|
+
this.capture(np, `${spec.name}-networkpolicy`);
|
|
882
|
+
return np;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Create a deny-all ingress NetworkPolicy for the current namespace
|
|
886
|
+
*/
|
|
887
|
+
addDenyAllIngressNetworkPolicy(name = 'deny-all-ingress') {
|
|
888
|
+
return this.addNetworkPolicy({
|
|
889
|
+
name,
|
|
890
|
+
podSelector: Rutter.EMPTY_POD_SELECTOR,
|
|
891
|
+
policyTypes: ['Ingress'],
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Create a deny-all egress NetworkPolicy for the current namespace
|
|
896
|
+
*/
|
|
897
|
+
addDenyAllEgressNetworkPolicy(name = 'deny-all-egress') {
|
|
898
|
+
return this.addNetworkPolicy({
|
|
899
|
+
name,
|
|
900
|
+
podSelector: Rutter.EMPTY_POD_SELECTOR,
|
|
901
|
+
policyTypes: ['Egress'],
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Create a deny-all NetworkPolicy for both ingress and egress
|
|
906
|
+
*/
|
|
907
|
+
addDenyAllNetworkPolicy(name = 'deny-all') {
|
|
908
|
+
return this.addNetworkPolicy({
|
|
909
|
+
name,
|
|
910
|
+
podSelector: Rutter.EMPTY_POD_SELECTOR,
|
|
911
|
+
policyTypes: ['Ingress', 'Egress'],
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Allow traffic from specific pods to target pods on specific ports
|
|
916
|
+
*/
|
|
917
|
+
addAllowFromPodsNetworkPolicy(spec) {
|
|
918
|
+
return this.addNetworkPolicy({
|
|
919
|
+
name: spec.name,
|
|
920
|
+
podSelector: { matchLabels: spec.targetPodSelector },
|
|
921
|
+
policyTypes: ['Ingress'],
|
|
922
|
+
ingress: [
|
|
923
|
+
{
|
|
924
|
+
from: [{ podSelector: { matchLabels: spec.sourcePodSelector } }],
|
|
925
|
+
...(spec.ports ? { ports: spec.ports } : {}),
|
|
926
|
+
},
|
|
927
|
+
],
|
|
928
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
929
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Allow traffic from specific namespace to target pods
|
|
934
|
+
*/
|
|
935
|
+
addAllowFromNamespaceNetworkPolicy(spec) {
|
|
936
|
+
return this.addNetworkPolicy({
|
|
937
|
+
name: spec.name,
|
|
938
|
+
podSelector: { matchLabels: spec.targetPodSelector },
|
|
939
|
+
policyTypes: ['Ingress'],
|
|
940
|
+
ingress: [
|
|
941
|
+
{
|
|
942
|
+
from: [{ namespaceSelector: { matchLabels: spec.sourceNamespaceSelector } }],
|
|
943
|
+
...(spec.ports ? { ports: spec.ports } : {}),
|
|
944
|
+
},
|
|
945
|
+
],
|
|
946
|
+
...(spec.labels ? { labels: spec.labels } : {}),
|
|
947
|
+
...(spec.annotations ? { annotations: spec.annotations } : {}),
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
validateNetworkPolicyTypes(spec) {
|
|
951
|
+
if (!spec.policyTypes || spec.policyTypes.length === 0) {
|
|
952
|
+
throw new Error(`NetworkPolicy ${spec.name}: policyTypes must be specified`);
|
|
953
|
+
}
|
|
954
|
+
this.validatePolicyTypeValues(spec);
|
|
955
|
+
this.validatePolicyTypeRules(spec);
|
|
956
|
+
}
|
|
957
|
+
validatePolicyTypeValues(spec) {
|
|
958
|
+
const validTypes = ['Ingress', 'Egress'];
|
|
959
|
+
for (const type of spec.policyTypes) {
|
|
960
|
+
if (!validTypes.includes(type)) {
|
|
961
|
+
throw new Error(`NetworkPolicy ${spec.name}: invalid policyType '${type}', must be 'Ingress' or 'Egress'`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
validatePolicyTypeRules(spec) {
|
|
966
|
+
if (spec.policyTypes.includes('Ingress') && spec.ingress === undefined) {
|
|
967
|
+
console.warn(`NetworkPolicy ${spec.name}: policyType 'Ingress' specified but no ingress rules defined (will deny all ingress)`);
|
|
968
|
+
}
|
|
969
|
+
if (spec.policyTypes.includes('Egress') && spec.egress === undefined) {
|
|
970
|
+
console.warn(`NetworkPolicy ${spec.name}: policyType 'Egress' specified but no egress rules defined (will deny all egress)`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
validateNetworkPolicyCIDRs(spec) {
|
|
974
|
+
const validateCIDR = (cidr, context) => {
|
|
975
|
+
if (!this.isValidCIDR(cidr)) {
|
|
976
|
+
throw new Error(`NetworkPolicy ${spec.name}: invalid CIDR '${cidr}' in ${context}`);
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
this.validateIngressCIDRs(spec, validateCIDR);
|
|
980
|
+
this.validateEgressCIDRs(spec, validateCIDR);
|
|
981
|
+
}
|
|
982
|
+
isValidCIDR(cidr) {
|
|
983
|
+
// IPv4 CIDR validation
|
|
984
|
+
const ipv4Parts = cidr.split('/');
|
|
985
|
+
if (ipv4Parts.length === 2) {
|
|
986
|
+
const [ip, prefix] = ipv4Parts;
|
|
987
|
+
if (ip && prefix && this.isValidIPv4(ip) && this.isValidPrefix(prefix, 32)) {
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
// IPv6 CIDR validation (basic)
|
|
992
|
+
if (cidr.includes(':') && ipv4Parts.length === 2) {
|
|
993
|
+
const [, prefix] = ipv4Parts;
|
|
994
|
+
if (prefix) {
|
|
995
|
+
return this.isValidPrefix(prefix, 128);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
isValidIPv4(ip) {
|
|
1001
|
+
const parts = ip.split('.');
|
|
1002
|
+
if (parts.length !== 4)
|
|
1003
|
+
return false;
|
|
1004
|
+
return parts.every((part) => {
|
|
1005
|
+
const num = parseInt(part, 10);
|
|
1006
|
+
return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
isValidPrefix(prefix, maxPrefix) {
|
|
1010
|
+
const num = parseInt(prefix, 10);
|
|
1011
|
+
return !isNaN(num) && num >= 0 && num <= maxPrefix && String(num) === prefix;
|
|
1012
|
+
}
|
|
1013
|
+
validateIngressCIDRs(spec, validateCIDR) {
|
|
1014
|
+
if (!spec.ingress)
|
|
1015
|
+
return;
|
|
1016
|
+
for (const rule of spec.ingress) {
|
|
1017
|
+
if (rule.from) {
|
|
1018
|
+
for (const peer of rule.from) {
|
|
1019
|
+
this.validatePeerCIDRs(peer, validateCIDR, 'ingress rule');
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
validateEgressCIDRs(spec, validateCIDR) {
|
|
1025
|
+
if (!spec.egress)
|
|
1026
|
+
return;
|
|
1027
|
+
for (const rule of spec.egress) {
|
|
1028
|
+
if (rule.to) {
|
|
1029
|
+
for (const peer of rule.to) {
|
|
1030
|
+
this.validatePeerCIDRs(peer, validateCIDR, 'egress rule');
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
validatePeerCIDRs(peer, validateCIDR, context) {
|
|
1036
|
+
if (peer.ipBlock?.cidr) {
|
|
1037
|
+
validateCIDR(peer.ipBlock.cidr, context);
|
|
1038
|
+
if (peer.ipBlock.except) {
|
|
1039
|
+
for (const except of peer.ipBlock.except) {
|
|
1040
|
+
validateCIDR(except, `${context} except block`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
478
1045
|
/**
|
|
479
1046
|
* Capture the YAML of an ApiObject or Construct into assets.
|
|
480
1047
|
*/
|
|
@@ -483,6 +1050,21 @@ class Rutter {
|
|
|
483
1050
|
// We defer synth until write(), but we can capture logical ids now
|
|
484
1051
|
this.assets.push({ id, yaml: `# captured:${id}` });
|
|
485
1052
|
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Extract a meaningful name from a Kubernetes resource object
|
|
1055
|
+
*/
|
|
1056
|
+
getResourceName(obj) {
|
|
1057
|
+
if (obj && typeof obj === 'object') {
|
|
1058
|
+
const resource = obj;
|
|
1059
|
+
if (resource.kind && resource.metadata?.name) {
|
|
1060
|
+
return `${resource.kind.toLowerCase()}-${resource.metadata.name}`;
|
|
1061
|
+
}
|
|
1062
|
+
if (resource.kind) {
|
|
1063
|
+
return resource.kind.toLowerCase();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return 'resource';
|
|
1067
|
+
}
|
|
486
1068
|
/**
|
|
487
1069
|
* Add a raw CRD manifest to the chart (written under crds/).
|
|
488
1070
|
*/
|
|
@@ -494,7 +1076,7 @@ class Rutter {
|
|
|
494
1076
|
*/
|
|
495
1077
|
write(outDir) {
|
|
496
1078
|
// Use cdk8s Testing.synth to obtain manifest objects
|
|
497
|
-
const manifestObjs =
|
|
1079
|
+
const manifestObjs = Testing.synth(this.chart);
|
|
498
1080
|
// Enforce common labels best-practice on all rendered objects
|
|
499
1081
|
const enriched = manifestObjs.map((obj) => {
|
|
500
1082
|
if (obj && typeof obj === 'object') {
|
|
@@ -504,11 +1086,11 @@ class Rutter {
|
|
|
504
1086
|
const labels = o.metadata.labels;
|
|
505
1087
|
const defaults = {
|
|
506
1088
|
'helm.sh/chart': '{{ .Chart.Name }}-{{ .Chart.Version }}',
|
|
507
|
-
'app.kubernetes.io/name':
|
|
508
|
-
'app.kubernetes.io/instance':
|
|
509
|
-
'app.kubernetes.io/version':
|
|
1089
|
+
'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
|
|
1090
|
+
'app.kubernetes.io/instance': helm.releaseName,
|
|
1091
|
+
'app.kubernetes.io/version': helm.chartVersion,
|
|
510
1092
|
'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
|
|
511
|
-
'app.kubernetes.io/part-of':
|
|
1093
|
+
'app.kubernetes.io/part-of': helm.chartName,
|
|
512
1094
|
};
|
|
513
1095
|
for (const [k, v] of Object.entries(defaults)) {
|
|
514
1096
|
if (!(k in labels)) {
|
|
@@ -519,13 +1101,30 @@ class Rutter {
|
|
|
519
1101
|
}
|
|
520
1102
|
return obj;
|
|
521
1103
|
});
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
1104
|
+
if (this.props.singleManifestFile) {
|
|
1105
|
+
// Combine all resources into a single manifest file
|
|
1106
|
+
const combinedYaml = enriched
|
|
1107
|
+
.map((obj) => YAML.stringify(obj).trim())
|
|
1108
|
+
.filter(Boolean)
|
|
1109
|
+
.join('\n---\n');
|
|
1110
|
+
this.assets.length = 0;
|
|
1111
|
+
const manifestId = this.props.manifestName ?? 'manifests';
|
|
1112
|
+
this.assets.push({ id: manifestId, yaml: combinedYaml, singleFile: true });
|
|
1113
|
+
}
|
|
1114
|
+
else {
|
|
1115
|
+
// Create separate files for each resource (default behavior)
|
|
1116
|
+
this.assets.length = 0;
|
|
1117
|
+
enriched.forEach((obj, _index) => {
|
|
1118
|
+
const yaml = YAML.stringify(obj).trim();
|
|
1119
|
+
if (yaml) {
|
|
1120
|
+
const resourceName = this.getResourceName(obj);
|
|
1121
|
+
const manifestId = this.props.manifestName
|
|
1122
|
+
? `${this.props.manifestName}-${resourceName}`
|
|
1123
|
+
: resourceName;
|
|
1124
|
+
this.assets.push({ id: manifestId, yaml });
|
|
1125
|
+
}
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
529
1128
|
const options = {
|
|
530
1129
|
outDir,
|
|
531
1130
|
meta: this.props.meta,
|
|
@@ -573,11 +1172,12 @@ class Rutter {
|
|
|
573
1172
|
...(this.props.notesTpl !== undefined ? { notesTpl: this.props.notesTpl } : {}),
|
|
574
1173
|
...(this.props.valuesSchema !== undefined ? { valuesSchema: this.props.valuesSchema } : {}),
|
|
575
1174
|
};
|
|
576
|
-
|
|
1175
|
+
HelmChartWriter.write(options);
|
|
577
1176
|
}
|
|
578
1177
|
}
|
|
579
|
-
exports.Rutter = Rutter;
|
|
580
1178
|
Rutter.LABEL_NAME = 'app.kubernetes.io/name';
|
|
581
1179
|
Rutter.LABEL_INSTANCE = 'app.kubernetes.io/instance';
|
|
582
1180
|
Rutter.HELPER_NAME = 'timonel.name';
|
|
1181
|
+
Rutter.NETWORKING_API_VERSION = 'networking.k8s.io/v1';
|
|
1182
|
+
Rutter.EMPTY_POD_SELECTOR = {};
|
|
583
1183
|
//# sourceMappingURL=Rutter.js.map
|