timonel 2.9.1 → 2.9.2
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 +6 -0
- package/dist/cli.js +69 -44
- package/dist/lib/helmChartWriter.js +4 -4
- package/dist/lib/rutter.js +10 -18
- package/dist/lib/templates/basic-chart.js +21 -0
- package/dist/lib/templates/subchart.js +46 -48
- package/dist/lib/templates/umbrella-chart.js +10 -55
- package/dist/lib/umbrellaRutter.js +4 -4
- package/dist/lib/utils/helmYamlSerializer.d.ts +8 -0
- package/dist/lib/utils/helmYamlSerializer.js +88 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [2.9.2](https://github.com/KenkoGeek/timonel/compare/v2.9.1...v2.9.2) (2025-09-19)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- **helm:** implement Helm-aware YAML serializer ([#112](https://github.com/KenkoGeek/timonel/issues/112)) ([daf4276](https://github.com/KenkoGeek/timonel/commit/daf427618eea73db832682f6b37287a9176bc31e))
|
|
6
|
+
|
|
1
7
|
## [2.9.1](https://github.com/KenkoGeek/timonel/compare/v2.9.0...v2.9.1) (2025-09-19)
|
|
2
8
|
|
|
3
9
|
### Bug Fixes
|
package/dist/cli.js
CHANGED
|
@@ -13,41 +13,50 @@ function log(msg, silent = false) {
|
|
|
13
13
|
console.log(msg);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function logError(msg, silent = false) {
|
|
17
|
+
if (!silent) {
|
|
18
|
+
console.error(msg);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function usageAndExit(msg, silent = false) {
|
|
17
22
|
if (msg) {
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
logError(`Error: ${msg}`, silent);
|
|
24
|
+
}
|
|
25
|
+
if (!silent) {
|
|
26
|
+
console.log([
|
|
27
|
+
'Usage: tl <command> [options]',
|
|
28
|
+
'',
|
|
29
|
+
'Commands:',
|
|
30
|
+
' tl init <chart-name> Create new chart',
|
|
31
|
+
' tl synth [outDir] Generate Helm chart',
|
|
32
|
+
' tl validate Validate chart',
|
|
33
|
+
' tl deploy <release> [namespace] Deploy chart',
|
|
34
|
+
' tl templates List available templates',
|
|
35
|
+
' tl help Show this help message',
|
|
36
|
+
'',
|
|
37
|
+
'Umbrella Charts:',
|
|
38
|
+
' tl umbrella init <n> Create umbrella chart structure',
|
|
39
|
+
' tl umbrella add <subchart> Add subchart to umbrella',
|
|
40
|
+
' tl umbrella synth [outDir] Generate umbrella chart',
|
|
41
|
+
'',
|
|
42
|
+
'Flags:',
|
|
43
|
+
' --dry-run Show what would be done without executing',
|
|
44
|
+
' --silent Suppress output (useful for CI)',
|
|
45
|
+
' --env <environment> Use environment-specific values',
|
|
46
|
+
' --set <key=value> Override values (can be used multiple times)',
|
|
47
|
+
' --help, -h Show this help message',
|
|
48
|
+
' --version, -v Show version information',
|
|
49
|
+
'',
|
|
50
|
+
'Examples:',
|
|
51
|
+
' tl init my-app',
|
|
52
|
+
' tl synth my-app my-app/dist',
|
|
53
|
+
' tl validate my-app',
|
|
54
|
+
' tl deploy my-app my-release --env prod',
|
|
55
|
+
' tl synth --dry-run --silent',
|
|
56
|
+
' tl synth --set replicas=5 --set image.tag=v2.0.0',
|
|
57
|
+
' tl deploy my-app my-release --set service.port=8080',
|
|
58
|
+
].join('\n'));
|
|
59
|
+
}
|
|
51
60
|
process.exit(msg ? 1 : 0);
|
|
52
61
|
}
|
|
53
62
|
async function cmdInit(name, silent = false) {
|
|
@@ -165,14 +174,13 @@ async function cmdTemplates(flags) {
|
|
|
165
174
|
},
|
|
166
175
|
];
|
|
167
176
|
if (flags?.silent) {
|
|
168
|
-
console.log(JSON.stringify(templates));
|
|
177
|
+
console.log(JSON.stringify(templates, null, 2));
|
|
169
178
|
}
|
|
170
179
|
else {
|
|
171
180
|
console.log('Available templates:');
|
|
172
181
|
templates.forEach((t) => {
|
|
173
|
-
console.log(
|
|
174
|
-
console.log(`
|
|
175
|
-
console.log(` Usage: ${t.usage}`);
|
|
182
|
+
console.log(` ${t.name.padEnd(15)} - ${t.description}`);
|
|
183
|
+
console.log(` ${''.padEnd(15)} Usage: ${t.usage}`);
|
|
176
184
|
});
|
|
177
185
|
}
|
|
178
186
|
}
|
|
@@ -388,15 +396,17 @@ function parseFlags(args) {
|
|
|
388
396
|
}
|
|
389
397
|
case '--version':
|
|
390
398
|
case '-v':
|
|
391
|
-
|
|
399
|
+
if (!flags.silent) {
|
|
400
|
+
console.log('Timonel v0.1.0');
|
|
401
|
+
}
|
|
392
402
|
process.exit(0);
|
|
393
403
|
break;
|
|
394
404
|
case '--help':
|
|
395
405
|
case '-h':
|
|
396
|
-
usageAndExit();
|
|
406
|
+
usageAndExit(undefined, flags.silent);
|
|
397
407
|
break;
|
|
398
408
|
default:
|
|
399
|
-
usageAndExit(`Unknown flag: ${flag}
|
|
409
|
+
usageAndExit(`Unknown flag: ${flag}`, flags.silent);
|
|
400
410
|
}
|
|
401
411
|
}
|
|
402
412
|
return flags;
|
|
@@ -421,20 +431,35 @@ async function executeCommand(command, args, flags) {
|
|
|
421
431
|
case 'umbrella':
|
|
422
432
|
await cmdUmbrella(args[0], args.slice(1), flags);
|
|
423
433
|
break;
|
|
434
|
+
case 'help':
|
|
424
435
|
case undefined:
|
|
425
|
-
usageAndExit(
|
|
436
|
+
usageAndExit(undefined, flags.silent);
|
|
426
437
|
break;
|
|
427
438
|
default:
|
|
428
|
-
usageAndExit(`Unknown command: ${command}
|
|
439
|
+
usageAndExit(`Unknown command: ${command}`, flags.silent);
|
|
429
440
|
}
|
|
430
441
|
}
|
|
431
442
|
async function main() {
|
|
432
443
|
const args = process.argv.slice(2);
|
|
444
|
+
const isSilent = args.includes('--silent');
|
|
445
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
446
|
+
usageAndExit(undefined, isSilent);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
450
|
+
if (!isSilent) {
|
|
451
|
+
console.log('Timonel v0.1.0');
|
|
452
|
+
}
|
|
453
|
+
process.exit(0);
|
|
454
|
+
}
|
|
433
455
|
const command = args.shift();
|
|
434
456
|
const flags = parseFlags(args);
|
|
435
457
|
await executeCommand(command, args, flags);
|
|
436
458
|
}
|
|
437
459
|
main().catch((error) => {
|
|
438
|
-
|
|
460
|
+
const flags = parseFlags(process.argv.slice(2));
|
|
461
|
+
if (!flags.silent) {
|
|
462
|
+
console.error('Error:', error.message);
|
|
463
|
+
}
|
|
439
464
|
process.exit(1);
|
|
440
465
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import YAML from 'yaml';
|
|
4
3
|
import { SecurityUtils } from './security.js';
|
|
4
|
+
import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
|
|
5
5
|
export class HelmChartWriter {
|
|
6
6
|
static write(opts) {
|
|
7
7
|
const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
|
|
@@ -21,7 +21,7 @@ export class HelmChartWriter {
|
|
|
21
21
|
fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
|
|
22
22
|
}
|
|
23
23
|
static writeChartYaml(outDir, meta) {
|
|
24
|
-
const chartYaml =
|
|
24
|
+
const chartYaml = dumpHelmAwareYaml({
|
|
25
25
|
apiVersion: 'v2',
|
|
26
26
|
name: meta.name,
|
|
27
27
|
version: meta.version,
|
|
@@ -39,10 +39,10 @@ export class HelmChartWriter {
|
|
|
39
39
|
fs.writeFileSync(path.join(outDir, 'Chart.yaml'), chartYaml);
|
|
40
40
|
}
|
|
41
41
|
static writeValuesFiles(outDir, defaultValues, envValues) {
|
|
42
|
-
fs.writeFileSync(path.join(outDir, 'values.yaml'),
|
|
42
|
+
fs.writeFileSync(path.join(outDir, 'values.yaml'), dumpHelmAwareYaml(defaultValues));
|
|
43
43
|
for (const [env, values] of Object.entries(envValues)) {
|
|
44
44
|
const sanitizedEnv = SecurityUtils.sanitizeEnvironmentName(env);
|
|
45
|
-
fs.writeFileSync(path.join(outDir, `values-${sanitizedEnv}.yaml`),
|
|
45
|
+
fs.writeFileSync(path.join(outDir, `values-${sanitizedEnv}.yaml`), dumpHelmAwareYaml(values));
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
static writeAssets(outDir, assets) {
|
package/dist/lib/rutter.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApiObject, App, Chart, Testing } from 'cdk8s';
|
|
2
|
-
import
|
|
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';
|
|
5
|
+
import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
|
|
6
6
|
import { AWSResources } from './resources/cloud/aws/awsResources.js';
|
|
7
7
|
import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
|
|
8
8
|
import { generateHelpersTemplate } from './utils/helmHelpers.js';
|
|
@@ -56,7 +56,7 @@ export class Rutter {
|
|
|
56
56
|
let manifestObject;
|
|
57
57
|
if (typeof yamlOrObject === 'string') {
|
|
58
58
|
try {
|
|
59
|
-
manifestObject =
|
|
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 =
|
|
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
|
|
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'],
|
|
@@ -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(
|
|
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(
|
|
223
|
+
const yaml = this.processHelmTemplates(dumpHelmAwareYaml(obj).trim());
|
|
232
224
|
if (yaml) {
|
|
233
225
|
synthAssets.push({ id: manifestId, yaml });
|
|
234
226
|
}
|
|
@@ -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');
|
|
@@ -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.
|
|
135
|
-
apiVersion: networking.k8s.io/v1
|
|
136
|
-
kind: Ingress
|
|
137
|
-
metadata:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { Chart, ApiObject } from 'cdk8s';
|
|
4
|
-
import
|
|
4
|
+
import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
|
|
5
|
+
import { generateHelpersTemplate } from '../utils/helmHelpers.js';
|
|
5
6
|
export function generateUmbrellaChart(name) {
|
|
6
7
|
return `import { App } from 'cdk8s';
|
|
7
8
|
import { UmbrellaChart } from 'timonel';
|
|
@@ -110,7 +111,7 @@ export class UmbrellaChartTemplate extends Chart {
|
|
|
110
111
|
repository: 'file://./charts/' + subchart.name,
|
|
111
112
|
})) || [],
|
|
112
113
|
};
|
|
113
|
-
writeFileSync(join(outputDir, 'Chart.yaml'),
|
|
114
|
+
writeFileSync(join(outputDir, 'Chart.yaml'), dumpHelmAwareYaml(chartYaml));
|
|
114
115
|
const valuesYaml = {
|
|
115
116
|
global: {
|
|
116
117
|
namespace: this.config.name,
|
|
@@ -122,7 +123,7 @@ export class UmbrellaChartTemplate extends Chart {
|
|
|
122
123
|
},
|
|
123
124
|
]) || []),
|
|
124
125
|
};
|
|
125
|
-
writeFileSync(join(outputDir, 'values.yaml'),
|
|
126
|
+
writeFileSync(join(outputDir, 'values.yaml'), dumpHelmAwareYaml(valuesYaml));
|
|
126
127
|
const chartsDir = join(outputDir, 'charts');
|
|
127
128
|
if (!existsSync(chartsDir)) {
|
|
128
129
|
mkdirSync(chartsDir, { recursive: true });
|
|
@@ -159,64 +160,18 @@ export class UmbrellaChartTemplate extends Chart {
|
|
|
159
160
|
metadata: {
|
|
160
161
|
name: '{{ .Values.global.namespace | default .Release.Name }}',
|
|
161
162
|
labels: {
|
|
162
|
-
'app.kubernetes.io/name': '{{
|
|
163
|
+
'app.kubernetes.io/name': '{{ .Chart.Name }}',
|
|
163
164
|
'app.kubernetes.io/instance': '{{ .Release.Name }}',
|
|
164
165
|
'app.kubernetes.io/version': '{{ .Chart.AppVersion }}',
|
|
165
166
|
'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
|
|
166
167
|
},
|
|
167
168
|
},
|
|
168
169
|
};
|
|
169
|
-
writeFileSync(join(templatesDir, 'namespace.yaml'),
|
|
170
|
-
const helpersTpl =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
|
175
|
-
{{- end }}
|
|
176
|
-
|
|
177
|
-
{{/*
|
|
178
|
-
Create a default fully qualified app name.
|
|
179
|
-
*/}}
|
|
180
|
-
{{- define "chart.fullname" -}}
|
|
181
|
-
{{- if .Values.fullnameOverride }}
|
|
182
|
-
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
|
183
|
-
{{- else }}
|
|
184
|
-
{{- $name := default .Chart.Name .Values.nameOverride }}
|
|
185
|
-
{{- if contains $name .Release.Name }}
|
|
186
|
-
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
|
187
|
-
{{- else }}
|
|
188
|
-
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
|
189
|
-
{{- end }}
|
|
190
|
-
{{- end }}
|
|
191
|
-
{{- end }}
|
|
192
|
-
|
|
193
|
-
{{/*
|
|
194
|
-
Create chart name and version as used by the chart label.
|
|
195
|
-
*/}}
|
|
196
|
-
{{- define "chart.chart" -}}
|
|
197
|
-
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
|
198
|
-
{{- end }}
|
|
199
|
-
|
|
200
|
-
{{/*
|
|
201
|
-
Common labels
|
|
202
|
-
*/}}
|
|
203
|
-
{{- define "chart.labels" -}}
|
|
204
|
-
helm.sh/chart: {{ include "chart.chart" . }}
|
|
205
|
-
{{ include "chart.selectorLabels" . }}
|
|
206
|
-
{{- if .Chart.AppVersion }}
|
|
207
|
-
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
208
|
-
{{- end }}
|
|
209
|
-
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
210
|
-
{{- end }}
|
|
211
|
-
|
|
212
|
-
{{/*
|
|
213
|
-
Selector labels
|
|
214
|
-
*/}}
|
|
215
|
-
{{- define "chart.selectorLabels" -}}
|
|
216
|
-
app.kubernetes.io/name: {{ include "chart.name" . }}
|
|
217
|
-
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
218
|
-
{{- end }}
|
|
219
|
-
`;
|
|
170
|
+
writeFileSync(join(templatesDir, 'namespace.yaml'), dumpHelmAwareYaml(namespaceYaml));
|
|
171
|
+
const helpersTpl = generateHelpersTemplate('aws', undefined, {
|
|
172
|
+
includeKubernetes: true,
|
|
173
|
+
includeSprig: true,
|
|
174
|
+
});
|
|
220
175
|
writeFileSync(join(templatesDir, '_helpers.tpl'), helpersTpl);
|
|
221
176
|
}
|
|
222
177
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import YAML from 'yaml';
|
|
4
3
|
import { SecurityUtils } from './security.js';
|
|
4
|
+
import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
|
|
5
5
|
export class UmbrellaRutter {
|
|
6
6
|
constructor(props) {
|
|
7
7
|
this.validateMetadata(props.meta);
|
|
@@ -54,7 +54,7 @@ export class UmbrellaRutter {
|
|
|
54
54
|
};
|
|
55
55
|
}),
|
|
56
56
|
};
|
|
57
|
-
writeFileSync(join(outDir, 'Chart.yaml'),
|
|
57
|
+
writeFileSync(join(outDir, 'Chart.yaml'), dumpHelmAwareYaml(chartYaml));
|
|
58
58
|
}
|
|
59
59
|
writeParentValues(outDir) {
|
|
60
60
|
const values = {
|
|
@@ -68,12 +68,12 @@ export class UmbrellaRutter {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
writeFileSync(join(outDir, 'values.yaml'),
|
|
71
|
+
writeFileSync(join(outDir, 'values.yaml'), dumpHelmAwareYaml(values));
|
|
72
72
|
if (this.props.envValues) {
|
|
73
73
|
for (const [env, envVals] of Object.entries(this.props.envValues)) {
|
|
74
74
|
const sanitizedEnv = SecurityUtils.sanitizeEnvironmentName(env);
|
|
75
75
|
const envValues = this.deepMerge(values, envVals);
|
|
76
|
-
writeFileSync(join(outDir, `values-${sanitizedEnv}.yaml`),
|
|
76
|
+
writeFileSync(join(outDir, `values-${sanitizedEnv}.yaml`), dumpHelmAwareYaml(envValues));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as jsYaml from 'js-yaml';
|
|
2
|
+
export declare function dumpHelmAwareYaml(obj: unknown, options?: jsYaml.DumpOptions): string;
|
|
3
|
+
export declare function stringify(obj: unknown, options?: {
|
|
4
|
+
lineWidth?: number;
|
|
5
|
+
doubleQuotedAsJSON?: boolean;
|
|
6
|
+
simpleKeys?: boolean;
|
|
7
|
+
}): string;
|
|
8
|
+
export declare function validateHelmExpressions(yaml: string): boolean;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as jsYaml from 'js-yaml';
|
|
2
|
+
const HELM_EXPRESSION_PATTERNS = [
|
|
3
|
+
/\{\{\s*[^}]+\s*\}\}/g,
|
|
4
|
+
/\{\{\s*include\s+[^}]+\s*\}\}/g,
|
|
5
|
+
/\{\{\s*if\s+[^}]+\s*\}\}/g,
|
|
6
|
+
/\{\{\s*else\s*\}\}/g,
|
|
7
|
+
/\{\{\s*end\s*\}\}/g,
|
|
8
|
+
/\{\{\s*range\s+[^}]+\s*\}\}/g,
|
|
9
|
+
/\{\{\s*with\s+[^}]+\s*\}\}/g,
|
|
10
|
+
/\{\{\s*\$[^}]+\s*\}\}/g,
|
|
11
|
+
/\{\{\s*[^}]*\|\s*[^}]+\s*\}\}/g,
|
|
12
|
+
];
|
|
13
|
+
function createHelmExpression(value) {
|
|
14
|
+
return {
|
|
15
|
+
__helmExpression: true,
|
|
16
|
+
value,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function isHelmExpression(value) {
|
|
20
|
+
return (typeof value === 'object' &&
|
|
21
|
+
value !== null &&
|
|
22
|
+
'__helmExpression' in value &&
|
|
23
|
+
value.__helmExpression === true);
|
|
24
|
+
}
|
|
25
|
+
function containsHelmExpression(str) {
|
|
26
|
+
return HELM_EXPRESSION_PATTERNS.some((pattern) => pattern.test(str));
|
|
27
|
+
}
|
|
28
|
+
function preprocessHelmExpressions(obj) {
|
|
29
|
+
if (typeof obj === 'string') {
|
|
30
|
+
if (containsHelmExpression(obj)) {
|
|
31
|
+
return createHelmExpression(obj);
|
|
32
|
+
}
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(obj)) {
|
|
36
|
+
return obj.map(preprocessHelmExpressions);
|
|
37
|
+
}
|
|
38
|
+
if (obj && typeof obj === 'object') {
|
|
39
|
+
const result = {};
|
|
40
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
41
|
+
Object.defineProperty(result, key, {
|
|
42
|
+
value: preprocessHelmExpressions(value),
|
|
43
|
+
writable: true,
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
return obj;
|
|
51
|
+
}
|
|
52
|
+
function helmAwareReplacer(key, value) {
|
|
53
|
+
if (isHelmExpression(value)) {
|
|
54
|
+
return value.value;
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
export function dumpHelmAwareYaml(obj, options = {}) {
|
|
59
|
+
const preprocessed = preprocessHelmExpressions(obj);
|
|
60
|
+
const helmOptions = {
|
|
61
|
+
forceQuotes: false,
|
|
62
|
+
lineWidth: options.lineWidth ?? 0,
|
|
63
|
+
flowLevel: options.flowLevel ?? -1,
|
|
64
|
+
replacer: helmAwareReplacer,
|
|
65
|
+
...options,
|
|
66
|
+
};
|
|
67
|
+
let yamlOutput = jsYaml.dump(preprocessed, helmOptions);
|
|
68
|
+
yamlOutput = postProcessHelmExpressions(yamlOutput);
|
|
69
|
+
return yamlOutput;
|
|
70
|
+
}
|
|
71
|
+
function postProcessHelmExpressions(yaml) {
|
|
72
|
+
let processed = yaml;
|
|
73
|
+
processed = processed.replace(/'(\{\{[^}]*\}\})'/g, '$1');
|
|
74
|
+
processed = processed.replace(/"(\{\{[^}]*\}\})"/g, '$1');
|
|
75
|
+
processed = processed.replace(/\\(\{\{[^}]*\}\})/g, '$1');
|
|
76
|
+
return processed;
|
|
77
|
+
}
|
|
78
|
+
export function stringify(obj, options = {}) {
|
|
79
|
+
const jsYamlOptions = {
|
|
80
|
+
lineWidth: options.lineWidth ?? 0,
|
|
81
|
+
quotingType: options.doubleQuotedAsJSON ? '"' : "'",
|
|
82
|
+
};
|
|
83
|
+
return dumpHelmAwareYaml(obj, jsYamlOptions);
|
|
84
|
+
}
|
|
85
|
+
export function validateHelmExpressions(yaml) {
|
|
86
|
+
const quotedHelmExpressions = [/'(\{\{[^}]*\}\})'/g, /"(\{\{[^}]*\}\})"/g];
|
|
87
|
+
return !quotedHelmExpressions.some((pattern) => pattern.test(yaml));
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "timonel",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.9.
|
|
4
|
+
"version": "2.9.2",
|
|
5
5
|
"description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
|
|
6
6
|
"bin": {
|
|
7
7
|
"timonel": "dist/cli.js",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"cdk8s-plus-33": "^2.3.5",
|
|
86
86
|
"constructs": "^10.4.2",
|
|
87
87
|
"handlebars": "^4.7.8",
|
|
88
|
+
"js-yaml": "^4.1.0",
|
|
88
89
|
"timonel": "^2.9.0",
|
|
89
90
|
"ts-node": "^10.9.2",
|
|
90
91
|
"yaml": "^2.8.1"
|
|
@@ -96,6 +97,7 @@
|
|
|
96
97
|
"@semantic-release/changelog": "^6.0.3",
|
|
97
98
|
"@semantic-release/exec": "^7.1.0",
|
|
98
99
|
"@semantic-release/git": "^10.0.1",
|
|
100
|
+
"@types/js-yaml": "^4.0.9",
|
|
99
101
|
"@types/node": "^24.3.0",
|
|
100
102
|
"@typescript-eslint/eslint-plugin": "^8.42.0",
|
|
101
103
|
"@typescript-eslint/parser": "^8.42.0",
|