timonel 2.9.1 → 2.10.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,15 +1,15 @@
1
1
  import { ApiObject, App, Chart, Testing } from 'cdk8s';
2
- import Handlebars from 'handlebars';
3
- import YAML from 'yaml';
2
+ import * as jsYaml from 'js-yaml';
4
3
  import { include } from './helm.js';
5
4
  import { HelmChartWriter } from './helmChartWriter.js';
6
5
  import { AWSResources } from './resources/cloud/aws/awsResources.js';
7
6
  import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
7
+ import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
8
8
  import { generateHelpersTemplate } from './utils/helmHelpers.js';
9
9
  export class Rutter {
10
10
  constructor(props) {
11
11
  this.assets = [];
12
- console.log(`🚀 Initializing Rutter chart: ${props.meta.name} v${props.meta.version}`);
12
+ console.log(`Initializing chart: ${props.meta.name} v${props.meta.version}`);
13
13
  this.defaultValues = props.defaultValues ?? {};
14
14
  this.envValues = props.envValues ?? {};
15
15
  this.meta = props.meta;
@@ -56,7 +56,7 @@ export class Rutter {
56
56
  let manifestObject;
57
57
  if (typeof yamlOrObject === 'string') {
58
58
  try {
59
- manifestObject = YAML.parse(yamlOrObject);
59
+ manifestObject = jsYaml.load(yamlOrObject);
60
60
  }
61
61
  catch (error) {
62
62
  throw new Error(`Invalid YAML provided to addManifest(): ${error instanceof Error ? error.message : 'Unknown error'}`);
@@ -102,20 +102,12 @@ export class Rutter {
102
102
  helmCondition = `.Values.${condition}`;
103
103
  }
104
104
  try {
105
- const yamlContent = YAML.stringify(manifestObject, {
105
+ const yamlContent = dumpHelmAwareYaml(manifestObject, {
106
106
  lineWidth: 0,
107
- doubleQuotedAsJSON: false,
108
- simpleKeys: true,
109
- });
110
- const conditionalTemplate = `\\{{- if ${helmCondition} }}
111
- {{{manifestYaml}}}
112
- \\{{- end }}`;
113
- const template = Handlebars.compile(conditionalTemplate, {
114
- noEscape: true,
115
- });
116
- const conditionalYaml = template({
117
- manifestYaml: yamlContent.trim(),
118
107
  });
108
+ const conditionalYaml = `{{- if ${helmCondition} }}
109
+ ${yamlContent.trim()}
110
+ {{- end }}`;
119
111
  const conditionalAsset = {
120
112
  id,
121
113
  yaml: conditionalYaml,
@@ -124,7 +116,7 @@ export class Rutter {
124
116
  this.assets.push(conditionalAsset);
125
117
  }
126
118
  catch (error) {
127
- throw new Error(`Failed to compile Handlebars template for manifest '${id}': ${error instanceof Error ? error.message : 'Unknown error'}`);
119
+ throw new Error(`Failed to generate conditional template for manifest '${id}': ${error instanceof Error ? error.message : 'Unknown error'}`);
128
120
  }
129
121
  return new ApiObject(this.chart, `${id}-placeholder`, {
130
122
  apiVersion: manifestObject['apiVersion'],
@@ -142,24 +134,24 @@ export class Rutter {
142
134
  }
143
135
  validateManifestStructure(manifest) {
144
136
  if (!manifest.apiVersion) {
145
- console.error(' Manifest validation failed: Missing apiVersion');
137
+ console.error('Validation failed: Missing apiVersion');
146
138
  throw new Error('Manifest must have an apiVersion');
147
139
  }
148
140
  if (!manifest.kind) {
149
- console.error(' Manifest validation failed: Missing kind');
141
+ console.error('Validation failed: Missing kind');
150
142
  throw new Error('Manifest must have a kind');
151
143
  }
152
144
  if (!manifest.metadata) {
153
- console.error(' Manifest validation failed: Missing metadata');
145
+ console.error('Validation failed: Missing metadata');
154
146
  throw new Error('Manifest must have metadata');
155
147
  }
156
148
  const metadata = manifest.metadata;
157
149
  if (!metadata.name) {
158
- console.error(' Manifest validation failed: Missing metadata.name');
150
+ console.error('Validation failed: Missing metadata.name');
159
151
  throw new Error('Manifest metadata must have a name');
160
152
  }
161
153
  if (typeof metadata.name !== 'string') {
162
- console.error(' Manifest validation failed: metadata.name must be a string');
154
+ console.error('Validation failed: metadata.name must be a string');
163
155
  throw new Error('Manifest metadata.name must be a string');
164
156
  }
165
157
  }
@@ -201,7 +193,7 @@ export class Rutter {
201
193
  const labels = o.metadata.labels;
202
194
  const defaults = {
203
195
  'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
204
- 'app.kubernetes.io/name': `{{ ${include} "${Rutter.HELPER_NAME}" . }}`,
196
+ 'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
205
197
  'app.kubernetes.io/instance': '{{ .Release.Name }}',
206
198
  'app.kubernetes.io/version': '{{ .Chart.Version }}',
207
199
  'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
@@ -218,7 +210,7 @@ export class Rutter {
218
210
  const synthAssets = [];
219
211
  if (this.props.singleManifestFile) {
220
212
  const combinedYaml = enriched
221
- .map((obj) => this.processHelmTemplates(YAML.stringify(obj).trim()))
213
+ .map((obj) => this.processHelmTemplates(dumpHelmAwareYaml(obj).trim()))
222
214
  .filter(Boolean)
223
215
  .join('\n---\n');
224
216
  const manifestId = this.props.manifestPrefix ?? 'manifests';
@@ -228,7 +220,7 @@ export class Rutter {
228
220
  enriched.forEach((obj, index) => {
229
221
  const apiObjectId = apiObjectIds[index];
230
222
  const manifestId = apiObjectId || `manifest-${index + 1}`;
231
- const yaml = this.processHelmTemplates(YAML.stringify(obj).trim());
223
+ const yaml = this.processHelmTemplates(dumpHelmAwareYaml(obj).trim());
232
224
  if (yaml) {
233
225
  synthAssets.push({ id: manifestId, yaml });
234
226
  }
@@ -407,7 +399,7 @@ export class Rutter {
407
399
  return processed;
408
400
  }
409
401
  write(outDir) {
410
- console.log(`✍️ Writing Helm chart '${this.meta.name}' to: ${outDir}`);
402
+ console.log(`Writing chart '${this.meta.name}' to: ${outDir}`);
411
403
  let helpersContent;
412
404
  if (this.props.helpersTpl) {
413
405
  if (typeof this.props.helpersTpl === 'string') {
@@ -437,7 +429,7 @@ ${helper.template}
437
429
  assets: synthAssets,
438
430
  helpersTpl: helpersContent,
439
431
  });
440
- console.log(`✅ Helm chart '${this.meta.name}' written successfully`);
432
+ console.log(`Chart '${this.meta.name}' written successfully`);
441
433
  }
442
434
  }
443
435
  Rutter.HELPER_NAME = 'chart.name';
@@ -1,5 +1,6 @@
1
1
  import { Chart, ApiObject } from 'cdk8s';
2
2
  import { Rutter } from '../rutter.js';
3
+ import { generateHelpersTemplate } from '../utils/helmHelpers.js';
3
4
  export class BasicChart extends Chart {
4
5
  constructor(scope, id, props = {}) {
5
6
  super(scope, id, props);
@@ -24,6 +25,10 @@ export class BasicChart extends Chart {
24
25
  replicas,
25
26
  createNamespace,
26
27
  },
28
+ helpersTpl: generateHelpersTemplate('aws', undefined, {
29
+ includeKubernetes: true,
30
+ includeSprig: true,
31
+ }),
27
32
  });
28
33
  if (createNamespace) {
29
34
  this.rutter.addConditionalManifest({
@@ -41,6 +46,11 @@ export class BasicChart extends Chart {
41
46
  name: `{{ .Values.appName }}`,
42
47
  labels: {
43
48
  app: `{{ .Values.appName }}`,
49
+ 'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
50
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
51
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
52
+ 'app.kubernetes.io/version': `{{ .Chart.AppVersion }}`,
53
+ 'app.kubernetes.io/managed-by': `{{ .Release.Service }}`,
44
54
  },
45
55
  },
46
56
  spec: {
@@ -48,12 +58,16 @@ export class BasicChart extends Chart {
48
58
  selector: {
49
59
  matchLabels: {
50
60
  app: `{{ .Values.appName }}`,
61
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
62
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
51
63
  },
52
64
  },
53
65
  template: {
54
66
  metadata: {
55
67
  labels: {
56
68
  app: `{{ .Values.appName }}`,
69
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
70
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
57
71
  },
58
72
  },
59
73
  spec: {
@@ -79,6 +93,11 @@ export class BasicChart extends Chart {
79
93
  name: `{{ .Values.appName }}`,
80
94
  labels: {
81
95
  app: `{{ .Values.appName }}`,
96
+ 'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
97
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
98
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
99
+ 'app.kubernetes.io/version': `{{ .Chart.AppVersion }}`,
100
+ 'app.kubernetes.io/managed-by': `{{ .Release.Service }}`,
82
101
  },
83
102
  },
84
103
  spec: {
@@ -90,6 +109,8 @@ export class BasicChart extends Chart {
90
109
  ],
91
110
  selector: {
92
111
  app: `{{ .Values.appName }}`,
112
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
113
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
93
114
  },
94
115
  },
95
116
  }, 'service');
@@ -0,0 +1,27 @@
1
+ import { Chart } from 'cdk8s';
2
+ export declare class FlexibleSubchart extends Chart {
3
+ private readonly config;
4
+ private _constructs;
5
+ private _manifests;
6
+ constructor(scope: Chart, id: string, config: {
7
+ name: string;
8
+ version?: string;
9
+ description?: string;
10
+ construct?: unknown;
11
+ manifest?: unknown;
12
+ [key: string]: unknown;
13
+ });
14
+ addConstruct(construct: unknown, id?: string): void;
15
+ addManifest(manifest: unknown, id?: string): void;
16
+ private configure;
17
+ writeHelmChart(outputDir: string): void;
18
+ private _generateManifestTemplate;
19
+ getConstructs(): unknown[];
20
+ getManifests(): unknown[];
21
+ }
22
+ export declare function createFlexibleSubchart(scope: Chart, id: string, config: {
23
+ name: string;
24
+ version?: string;
25
+ description?: string;
26
+ [key: string]: unknown;
27
+ }): FlexibleSubchart;
@@ -0,0 +1,107 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { Chart } from 'cdk8s';
4
+ import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
5
+ import { generateHelpersTemplate } from '../utils/helmHelpers.js';
6
+ export class FlexibleSubchart extends Chart {
7
+ constructor(scope, id, config) {
8
+ super(scope, id);
9
+ this.config = config;
10
+ this._constructs = [];
11
+ this._manifests = [];
12
+ this.configure();
13
+ }
14
+ addConstruct(construct, id) {
15
+ if (construct) {
16
+ this._constructs.push({ construct, id: id || `construct-${this._constructs.length}` });
17
+ }
18
+ }
19
+ addManifest(manifest, id) {
20
+ if (manifest) {
21
+ this._manifests.push({ manifest, id: id || `manifest-${this._manifests.length}` });
22
+ }
23
+ }
24
+ configure() {
25
+ if (this.config.construct) {
26
+ this.addConstruct(this.config.construct);
27
+ }
28
+ if (this.config.manifest) {
29
+ this.addManifest(this.config.manifest);
30
+ }
31
+ Object.entries(this.config).forEach(([key, value]) => {
32
+ if (key !== 'name' &&
33
+ key !== 'version' &&
34
+ key !== 'description' &&
35
+ key !== 'construct' &&
36
+ key !== 'manifest' &&
37
+ value) {
38
+ this.addConstruct(value, key);
39
+ }
40
+ });
41
+ }
42
+ writeHelmChart(outputDir) {
43
+ if (!existsSync(outputDir)) {
44
+ mkdirSync(outputDir, { recursive: true });
45
+ }
46
+ const subchartYaml = {
47
+ apiVersion: 'v2',
48
+ name: this.config.name,
49
+ description: this.config.description || `${this.config.name} subchart`,
50
+ type: 'application',
51
+ version: this.config.version || '1.0.0',
52
+ appVersion: this.config.version || '1.0.0',
53
+ };
54
+ writeFileSync(join(outputDir, 'Chart.yaml'), dumpHelmAwareYaml(subchartYaml));
55
+ const subchartValues = {
56
+ enabled: true,
57
+ ...Object.fromEntries(Object.entries(this.config).filter(([key, value]) => key !== 'chart' && typeof value !== 'function' && value !== null && value !== undefined)),
58
+ };
59
+ writeFileSync(join(outputDir, 'values.yaml'), dumpHelmAwareYaml(subchartValues));
60
+ const templatesDir = join(outputDir, 'templates');
61
+ if (!existsSync(templatesDir)) {
62
+ mkdirSync(templatesDir, { recursive: true });
63
+ }
64
+ const helpersTpl = generateHelpersTemplate('aws', undefined, {
65
+ includeKubernetes: true,
66
+ includeSprig: true,
67
+ });
68
+ writeFileSync(join(templatesDir, '_helpers.tpl'), helpersTpl);
69
+ this._manifests.forEach((item) => {
70
+ if (item && typeof item === 'object' && 'manifest' in item && 'id' in item) {
71
+ const { manifest, id } = item;
72
+ if (manifest && typeof manifest === 'object') {
73
+ const templateContent = this._generateManifestTemplate(manifest, id);
74
+ writeFileSync(join(templatesDir, `${id}.yaml`), templateContent);
75
+ }
76
+ }
77
+ });
78
+ console.log(`Created flexible Helm chart for subchart: ${this.config.name}`);
79
+ }
80
+ _generateManifestTemplate(manifest, id) {
81
+ if (typeof manifest !== 'object' || manifest === null) {
82
+ return dumpHelmAwareYaml({});
83
+ }
84
+ const manifestObj = manifest;
85
+ const template = {
86
+ apiVersion: manifestObj.apiVersion || 'v1',
87
+ kind: manifestObj.kind || 'Manifest',
88
+ metadata: {
89
+ name: `{{ include "${id}.fullname" . }}`,
90
+ labels: `{{ include "${id}.labels" . }}`,
91
+ ...(manifestObj.metadata || {}),
92
+ },
93
+ spec: manifestObj.spec || {},
94
+ ...manifestObj,
95
+ };
96
+ return dumpHelmAwareYaml(template);
97
+ }
98
+ getConstructs() {
99
+ return this._constructs;
100
+ }
101
+ getManifests() {
102
+ return this._manifests;
103
+ }
104
+ }
105
+ export function createFlexibleSubchart(scope, id, config) {
106
+ return new FlexibleSubchart(scope, id, config);
107
+ }
@@ -1,5 +1,6 @@
1
1
  import { App, Chart } from 'cdk8s';
2
2
  import { Rutter } from '../rutter.js';
3
+ import { generateHelpersTemplate } from '../utils/helmHelpers.js';
3
4
  export class Subchart extends Chart {
4
5
  constructor(scope, id, props = {}) {
5
6
  super(scope, id, props);
@@ -39,6 +40,10 @@ export class Subchart extends Chart {
39
40
  tls: [],
40
41
  },
41
42
  },
43
+ helpersTpl: generateHelpersTemplate('aws', undefined, {
44
+ includeKubernetes: true,
45
+ includeSprig: true,
46
+ }),
42
47
  });
43
48
  this.rutter.addManifest({
44
49
  apiVersion: 'v1',
@@ -131,54 +136,47 @@ export class Subchart extends Chart {
131
136
  },
132
137
  },
133
138
  }, 'service');
134
- this.rutter.addTemplateManifest(`{{- if .Values.enableIngress }}
135
- apiVersion: networking.k8s.io/v1
136
- kind: Ingress
137
- metadata:
138
- name: {{ include "chart.fullname" . }}
139
- labels:
140
- {{- include "chart.labels" . | nindent 4 }}
141
- {{- with .Values.ingress.annotations }}
142
- annotations:
143
- {{- toYaml . | nindent 4 }}
144
- {{- end }}
145
- spec:
146
- {{- if and .Values.ingress.className (not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class")) }}
147
- ingressClassName: {{ .Values.ingress.className }}
148
- {{- end }}
149
- {{- if .Values.ingress.tls }}
150
- tls:
151
- {{- range .Values.ingress.tls }}
152
- - hosts:
153
- {{- range .hosts }}
154
- - {{ . | quote }}
155
- {{- end }}
156
- secretName: {{ .secretName }}
157
- {{- end }}
158
- {{- end }}
159
- rules:
160
- {{- range .Values.ingress.hosts }}
161
- - host: {{ .host | quote }}
162
- http:
163
- paths:
164
- {{- range .paths }}
165
- - path: {{ .path }}
166
- {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
167
- pathType: {{ .pathType }}
168
- {{- end }}
169
- backend:
170
- {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
171
- service:
172
- name: {{ include "chart.fullname" $ }}
173
- port:
174
- number: {{ $.Values.port | int }}
175
- {{- else }}
176
- serviceName: {{ include "chart.fullname" $ }}
177
- servicePort: {{ $.Values.port | int }}
178
- {{- end }}
179
- {{- end }}
180
- {{- end }}
181
- {{- end }}`, 'ingress');
139
+ this.rutter.addConditionalManifest({
140
+ apiVersion: 'networking.k8s.io/v1',
141
+ kind: 'Ingress',
142
+ metadata: {
143
+ name: `{{ .Release.Name }}-{{ .Values.appName }}`,
144
+ labels: {
145
+ app: `{{ .Values.appName }}`,
146
+ 'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
147
+ 'app.kubernetes.io/name': `{{ .Chart.Name }}`,
148
+ 'app.kubernetes.io/instance': `{{ .Release.Name }}`,
149
+ 'app.kubernetes.io/version': `{{ .Chart.AppVersion }}`,
150
+ 'app.kubernetes.io/managed-by': `{{ .Release.Service }}`,
151
+ },
152
+ annotations: `{{ toYaml .Values.ingress.annotations | nindent 4 }}`,
153
+ },
154
+ spec: {
155
+ ingressClassName: `{{ .Values.ingress.className }}`,
156
+ tls: `{{ toYaml .Values.ingress.tls | nindent 4 }}`,
157
+ rules: [
158
+ {
159
+ host: `{{ .Values.ingressHost }}`,
160
+ http: {
161
+ paths: [
162
+ {
163
+ path: '/',
164
+ pathType: 'Prefix',
165
+ backend: {
166
+ service: {
167
+ name: `{{ .Release.Name }}-{{ .Values.appName }}`,
168
+ port: {
169
+ number: `{{ .Values.port }}`,
170
+ },
171
+ },
172
+ },
173
+ },
174
+ ],
175
+ },
176
+ },
177
+ ],
178
+ },
179
+ }, 'enableIngress', 'ingress');
182
180
  }
183
181
  get rutterInstance() {
184
182
  return this.rutter;
@@ -9,5 +9,6 @@ export declare class UmbrellaChartTemplate extends Chart {
9
9
  private configure;
10
10
  private _addService;
11
11
  private _addSubchart;
12
+ private _createBasicSubchart;
12
13
  writeHelmChart(outputDir: string): void;
13
14
  }