timonel 0.4.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.
@@ -1,23 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
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 cdk8s_1.App();
20
- this.chart = new cdk8s_1.Chart(this.app, props.manifestName ?? props.meta.name);
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]: (0, helm_1.include)(Rutter.HELPER_NAME),
72
- [Rutter.LABEL_INSTANCE]: helm_1.helm.releaseName,
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 cdk8s_1.ApiObject(this.chart, spec.name, {
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 cdk8s_1.ApiObject(this.chart, spec.name, {
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]: (0, helm_1.include)(Rutter.HELPER_NAME),
179
- [Rutter.LABEL_INSTANCE]: helm_1.helm.releaseName,
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 cdk8s_1.ApiObject(this.chart, spec.name, {
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 cdk8s_1.ApiObject(this.chart, spec.name, {
251
- apiVersion: 'networking.k8s.io/v1',
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 cdk8s_1.ApiObject(this.chart, spec.name, {
318
+ const pv = new ApiObject(this.chart, spec.name, {
325
319
  apiVersion: 'v1',
326
320
  kind: 'PersistentVolume',
327
321
  metadata: {
@@ -378,7 +372,7 @@ class Rutter {
378
372
  addPersistentVolumeClaim(spec) {
379
373
  // Validate access modes compatibility
380
374
  this.validatePVCAccessModes(spec);
381
- const pvc = new cdk8s_1.ApiObject(this.chart, spec.name, {
375
+ const pvc = new ApiObject(this.chart, spec.name, {
382
376
  apiVersion: 'v1',
383
377
  kind: 'PersistentVolumeClaim',
384
378
  metadata: {
@@ -413,7 +407,7 @@ class Rutter {
413
407
  }
414
408
  }
415
409
  addConfigMap(spec) {
416
- const cm = new cdk8s_1.ApiObject(this.chart, spec.name, {
410
+ const cm = new ApiObject(this.chart, spec.name, {
417
411
  apiVersion: 'v1',
418
412
  kind: 'ConfigMap',
419
413
  metadata: {
@@ -429,7 +423,7 @@ class Rutter {
429
423
  return cm;
430
424
  }
431
425
  addSecret(spec) {
432
- const sec = new cdk8s_1.ApiObject(this.chart, spec.name, {
426
+ const sec = new ApiObject(this.chart, spec.name, {
433
427
  apiVersion: 'v1',
434
428
  kind: 'Secret',
435
429
  metadata: {
@@ -447,7 +441,7 @@ class Rutter {
447
441
  }
448
442
  addServiceAccount(spec) {
449
443
  const annotations = this.buildServiceAccountAnnotations(spec);
450
- const sa = new cdk8s_1.ApiObject(this.chart, spec.name, {
444
+ const sa = new ApiObject(this.chart, spec.name, {
451
445
  apiVersion: 'v1',
452
446
  kind: 'ServiceAccount',
453
447
  metadata: {
@@ -515,7 +509,7 @@ class Rutter {
515
509
  },
516
510
  },
517
511
  ];
518
- const hpa = new cdk8s_1.ApiObject(this.chart, spec.name, {
512
+ const hpa = new ApiObject(this.chart, spec.name, {
519
513
  apiVersion: 'autoscaling/v2',
520
514
  kind: 'HorizontalPodAutoscaler',
521
515
  metadata: {
@@ -535,7 +529,7 @@ class Rutter {
535
529
  return hpa;
536
530
  }
537
531
  addVerticalPodAutoscaler(spec) {
538
- const vpa = new cdk8s_1.ApiObject(this.chart, spec.name, {
532
+ const vpa = new ApiObject(this.chart, spec.name, {
539
533
  apiVersion: 'autoscaling.k8s.io/v1',
540
534
  kind: 'VerticalPodAutoscaler',
541
535
  metadata: {
@@ -554,7 +548,7 @@ class Rutter {
554
548
  return vpa;
555
549
  }
556
550
  addPodDisruptionBudget(spec) {
557
- const pdb = new cdk8s_1.ApiObject(this.chart, spec.name, {
551
+ const pdb = new ApiObject(this.chart, spec.name, {
558
552
  apiVersion: 'policy/v1',
559
553
  kind: 'PodDisruptionBudget',
560
554
  metadata: {
@@ -588,7 +582,7 @@ class Rutter {
588
582
  if (spec.throughput && spec.volumeType === DEFAULT_VOLUME_TYPE) {
589
583
  parameters['throughput'] = String(spec.throughput);
590
584
  }
591
- const sc = new cdk8s_1.ApiObject(this.chart, spec.name, {
585
+ const sc = new ApiObject(this.chart, spec.name, {
592
586
  apiVersion: 'storage.k8s.io/v1',
593
587
  kind: 'StorageClass',
594
588
  metadata: {
@@ -606,7 +600,7 @@ class Rutter {
606
600
  return sc;
607
601
  }
608
602
  addAWSEBSPersistentVolumeClaim(spec) {
609
- const pvc = new cdk8s_1.ApiObject(this.chart, spec.name, {
603
+ const pvc = new ApiObject(this.chart, spec.name, {
610
604
  apiVersion: 'v1',
611
605
  kind: 'PersistentVolumeClaim',
612
606
  metadata: {
@@ -628,7 +622,7 @@ class Rutter {
628
622
  return pvc;
629
623
  }
630
624
  addAWSEFSStorageClass(spec) {
631
- const sc = new cdk8s_1.ApiObject(this.chart, spec.name, {
625
+ const sc = new ApiObject(this.chart, spec.name, {
632
626
  apiVersion: 'storage.k8s.io/v1',
633
627
  kind: 'StorageClass',
634
628
  metadata: {
@@ -670,7 +664,7 @@ class Rutter {
670
664
  if (Object.keys(volumeAttributes).length > 0) {
671
665
  csiSpec['volumeAttributes'] = volumeAttributes;
672
666
  }
673
- const pv = new cdk8s_1.ApiObject(this.chart, spec.name, {
667
+ const pv = new ApiObject(this.chart, spec.name, {
674
668
  apiVersion: 'v1',
675
669
  kind: 'PersistentVolume',
676
670
  metadata: {
@@ -692,7 +686,7 @@ class Rutter {
692
686
  return pv;
693
687
  }
694
688
  addAWSEFSPersistentVolumeClaim(spec) {
695
- const pvc = new cdk8s_1.ApiObject(this.chart, spec.name, {
689
+ const pvc = new ApiObject(this.chart, spec.name, {
696
690
  apiVersion: 'v1',
697
691
  kind: 'PersistentVolumeClaim',
698
692
  metadata: {
@@ -715,8 +709,8 @@ class Rutter {
715
709
  }
716
710
  addAWSALBIngress(spec) {
717
711
  const annotations = this.buildALBAnnotations(spec);
718
- const ingress = new cdk8s_1.ApiObject(this.chart, spec.name, {
719
- apiVersion: 'networking.k8s.io/v1',
712
+ const ingress = new ApiObject(this.chart, spec.name, {
713
+ apiVersion: Rutter.NETWORKING_API_VERSION,
720
714
  kind: 'Ingress',
721
715
  metadata: {
722
716
  name: spec.name,
@@ -812,7 +806,7 @@ class Rutter {
812
806
  : {}),
813
807
  ...(spec.annotations ?? {}),
814
808
  };
815
- const sa = new cdk8s_1.ApiObject(this.chart, spec.name, {
809
+ const sa = new ApiObject(this.chart, spec.name, {
816
810
  apiVersion: 'v1',
817
811
  kind: 'ServiceAccount',
818
812
  metadata: {
@@ -841,14 +835,14 @@ class Rutter {
841
835
  }
842
836
  return baseObj;
843
837
  });
844
- const objectsYaml = yaml_1.default.stringify(objects, { indent: 2 }).trim();
838
+ const objectsYaml = YAML.stringify(objects, { indent: 2 }).trim();
845
839
  const parameters = {
846
840
  objects: `|\n ${objectsYaml.split('\n').join('\n ')}`,
847
841
  };
848
842
  if (spec.region) {
849
843
  parameters['region'] = spec.region;
850
844
  }
851
- const spc = new cdk8s_1.ApiObject(this.chart, spec.name, {
845
+ const spc = new ApiObject(this.chart, spec.name, {
852
846
  apiVersion: 'secrets-store.csi.x-k8s.io/v1',
853
847
  kind: 'SecretProviderClass',
854
848
  metadata: {
@@ -864,6 +858,190 @@ class Rutter {
864
858
  this.capture(spc, `${spec.name}-secretproviderclass`);
865
859
  return spc;
866
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
+ }
867
1045
  /**
868
1046
  * Capture the YAML of an ApiObject or Construct into assets.
869
1047
  */
@@ -898,7 +1076,7 @@ class Rutter {
898
1076
  */
899
1077
  write(outDir) {
900
1078
  // Use cdk8s Testing.synth to obtain manifest objects
901
- const manifestObjs = cdk8s_1.Testing.synth(this.chart);
1079
+ const manifestObjs = Testing.synth(this.chart);
902
1080
  // Enforce common labels best-practice on all rendered objects
903
1081
  const enriched = manifestObjs.map((obj) => {
904
1082
  if (obj && typeof obj === 'object') {
@@ -908,11 +1086,11 @@ class Rutter {
908
1086
  const labels = o.metadata.labels;
909
1087
  const defaults = {
910
1088
  'helm.sh/chart': '{{ .Chart.Name }}-{{ .Chart.Version }}',
911
- 'app.kubernetes.io/name': (0, helm_1.include)(Rutter.HELPER_NAME),
912
- 'app.kubernetes.io/instance': helm_1.helm.releaseName,
913
- 'app.kubernetes.io/version': helm_1.helm.chartVersion,
1089
+ 'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
1090
+ 'app.kubernetes.io/instance': helm.releaseName,
1091
+ 'app.kubernetes.io/version': helm.chartVersion,
914
1092
  'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
915
- 'app.kubernetes.io/part-of': helm_1.helm.chartName,
1093
+ 'app.kubernetes.io/part-of': helm.chartName,
916
1094
  };
917
1095
  for (const [k, v] of Object.entries(defaults)) {
918
1096
  if (!(k in labels)) {
@@ -926,7 +1104,7 @@ class Rutter {
926
1104
  if (this.props.singleManifestFile) {
927
1105
  // Combine all resources into a single manifest file
928
1106
  const combinedYaml = enriched
929
- .map((obj) => yaml_1.default.stringify(obj).trim())
1107
+ .map((obj) => YAML.stringify(obj).trim())
930
1108
  .filter(Boolean)
931
1109
  .join('\n---\n');
932
1110
  this.assets.length = 0;
@@ -937,7 +1115,7 @@ class Rutter {
937
1115
  // Create separate files for each resource (default behavior)
938
1116
  this.assets.length = 0;
939
1117
  enriched.forEach((obj, _index) => {
940
- const yaml = yaml_1.default.stringify(obj).trim();
1118
+ const yaml = YAML.stringify(obj).trim();
941
1119
  if (yaml) {
942
1120
  const resourceName = this.getResourceName(obj);
943
1121
  const manifestId = this.props.manifestName
@@ -994,11 +1172,12 @@ class Rutter {
994
1172
  ...(this.props.notesTpl !== undefined ? { notesTpl: this.props.notesTpl } : {}),
995
1173
  ...(this.props.valuesSchema !== undefined ? { valuesSchema: this.props.valuesSchema } : {}),
996
1174
  };
997
- HelmChartWriter_1.HelmChartWriter.write(options);
1175
+ HelmChartWriter.write(options);
998
1176
  }
999
1177
  }
1000
- exports.Rutter = Rutter;
1001
1178
  Rutter.LABEL_NAME = 'app.kubernetes.io/name';
1002
1179
  Rutter.LABEL_INSTANCE = 'app.kubernetes.io/instance';
1003
1180
  Rutter.HELPER_NAME = 'timonel.name';
1181
+ Rutter.NETWORKING_API_VERSION = 'networking.k8s.io/v1';
1182
+ Rutter.EMPTY_POD_SELECTOR = {};
1004
1183
  //# sourceMappingURL=Rutter.js.map