timonel 2.13.0 → 3.0.0-beta.1
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 +191 -0
- package/README.md +203 -35
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/lib/helmChartWriter.js +5 -3
- package/dist/lib/rutter.d.ts +0 -12
- package/dist/lib/rutter.js +22 -188
- package/dist/lib/templates/flexible-subchart.js +23 -29
- package/dist/lib/templates/umbrella-chart.js +7 -7
- package/dist/lib/utils/envVarsLoader.d.ts +19 -0
- package/dist/lib/utils/envVarsLoader.js +57 -0
- package/dist/lib/utils/helmConstructSerializer.d.ts +3 -0
- package/dist/lib/utils/helmConstructSerializer.js +61 -0
- package/dist/lib/utils/helmControlStructures.d.ts +33 -0
- package/dist/lib/utils/helmControlStructures.js +124 -0
- package/dist/lib/utils/helmYamlSerializer.d.ts +10 -28
- package/dist/lib/utils/helmYamlSerializer.js +638 -200
- package/dist/lib/utils/valuesRef.d.ts +102 -0
- package/dist/lib/utils/valuesRef.js +264 -0
- package/package.json +10 -3
package/dist/lib/rutter.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ApiObject, App, Chart, Testing } from 'cdk8s';
|
|
2
|
-
import
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
3
|
import { include } from './helm.js';
|
|
4
4
|
import { HelmChartWriter } from './helmChartWriter.js';
|
|
5
5
|
import { AWSResources } from './resources/cloud/aws/awsResources.js';
|
|
6
6
|
import { createLogger } from './utils/logger.js';
|
|
7
7
|
import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
|
|
8
|
-
import {
|
|
8
|
+
import { isHelmExpression, isHelmConstruct } from './utils/helmControlStructures.js';
|
|
9
|
+
import { dumpHelmAwareYaml, preprocessHelmConstructs } from './utils/helmYamlSerializer.js';
|
|
9
10
|
import { generateHelpersTemplate } from './utils/helmHelpers.js';
|
|
10
11
|
export class Rutter {
|
|
11
12
|
constructor(props) {
|
|
@@ -63,7 +64,7 @@ export class Rutter {
|
|
|
63
64
|
let manifestObject;
|
|
64
65
|
if (typeof yamlOrObject === 'string') {
|
|
65
66
|
try {
|
|
66
|
-
manifestObject =
|
|
67
|
+
manifestObject = parse(yamlOrObject);
|
|
67
68
|
}
|
|
68
69
|
catch (error) {
|
|
69
70
|
throw new Error(`Invalid YAML provided to addManifest(): ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -76,12 +77,14 @@ export class Rutter {
|
|
|
76
77
|
throw new Error('addManifest() requires either a YAML string or an object');
|
|
77
78
|
}
|
|
78
79
|
this.validateManifestStructure(manifestObject);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
const preprocessedManifest = preprocessHelmConstructs(manifestObject);
|
|
81
|
+
const apiObjectProps = {
|
|
82
|
+
apiVersion: preprocessedManifest.apiVersion,
|
|
83
|
+
kind: preprocessedManifest.kind,
|
|
84
|
+
metadata: preprocessedManifest.metadata,
|
|
85
|
+
...preprocessedManifest,
|
|
86
|
+
};
|
|
87
|
+
return new ApiObject(this.chart, id, apiObjectProps);
|
|
85
88
|
}
|
|
86
89
|
addTemplateManifest(yamlTemplate, id) {
|
|
87
90
|
const templateAsset = {
|
|
@@ -169,13 +172,10 @@ ${yamlContent.trim()}
|
|
|
169
172
|
});
|
|
170
173
|
throw new Error('Manifest metadata must have a name');
|
|
171
174
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
actual_type: typeof metadata.name,
|
|
177
|
-
});
|
|
178
|
-
throw new Error('Manifest metadata.name must be a string or HelmExpression');
|
|
175
|
+
const name = manifest['metadata']?.['name'];
|
|
176
|
+
if (typeof name !== 'string' && !isHelmExpression(name) && !isHelmConstruct(name)) {
|
|
177
|
+
const actualType = Array.isArray(name) ? 'array' : name === null ? 'null' : typeof name;
|
|
178
|
+
throw new Error(`Manifest metadata.name must be a string, HelmExpression, or HelmConstruct. Got ${actualType}`);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
getMeta() {
|
|
@@ -218,8 +218,9 @@ ${yamlContent.trim()}
|
|
|
218
218
|
operation: 'manifest_processing',
|
|
219
219
|
});
|
|
220
220
|
const enriched = manifestObjs.map((obj) => {
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
const preprocessed = preprocessHelmConstructs(obj);
|
|
222
|
+
if (preprocessed && typeof preprocessed === 'object') {
|
|
223
|
+
const o = preprocessed;
|
|
223
224
|
o.metadata = o.metadata ?? {};
|
|
224
225
|
o.metadata.labels = o.metadata.labels ?? {};
|
|
225
226
|
const labels = o.metadata.labels;
|
|
@@ -237,12 +238,12 @@ ${yamlContent.trim()}
|
|
|
237
238
|
}
|
|
238
239
|
}
|
|
239
240
|
}
|
|
240
|
-
return
|
|
241
|
+
return preprocessed;
|
|
241
242
|
});
|
|
242
243
|
const synthAssets = [];
|
|
243
244
|
if (this.props.singleManifestFile) {
|
|
244
245
|
const combinedYaml = enriched
|
|
245
|
-
.map((obj) =>
|
|
246
|
+
.map((obj) => dumpHelmAwareYaml(obj).trim())
|
|
246
247
|
.filter(Boolean)
|
|
247
248
|
.join('\n---\n');
|
|
248
249
|
const manifestId = this.props.manifestPrefix ?? 'manifests';
|
|
@@ -252,7 +253,7 @@ ${yamlContent.trim()}
|
|
|
252
253
|
enriched.forEach((obj, index) => {
|
|
253
254
|
const apiObjectId = apiObjectIds[index];
|
|
254
255
|
const manifestId = apiObjectId || `manifest-${index + 1}`;
|
|
255
|
-
const yaml =
|
|
256
|
+
const yaml = dumpHelmAwareYaml(obj).trim();
|
|
256
257
|
if (yaml) {
|
|
257
258
|
synthAssets.push({ id: manifestId, yaml });
|
|
258
259
|
}
|
|
@@ -270,173 +271,6 @@ ${yamlContent.trim()}
|
|
|
270
271
|
timer();
|
|
271
272
|
return synthAssets;
|
|
272
273
|
}
|
|
273
|
-
processHelmTemplates(yaml) {
|
|
274
|
-
let processed = this.fixCharacterMappingIssues(yaml);
|
|
275
|
-
processed = this.applyHelmTemplateReplacements(processed);
|
|
276
|
-
return processed;
|
|
277
|
-
}
|
|
278
|
-
fixCharacterMappingIssues(yaml) {
|
|
279
|
-
const lines = yaml.split('\n');
|
|
280
|
-
const fixedLines = [];
|
|
281
|
-
let i = 0;
|
|
282
|
-
while (i < lines.length) {
|
|
283
|
-
const line = lines[i];
|
|
284
|
-
if (!line) {
|
|
285
|
-
i++;
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
const charKeyMatch = line.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
289
|
-
if (charKeyMatch && charKeyMatch[2] && parseInt(charKeyMatch[2]) === 0) {
|
|
290
|
-
const indent = charKeyMatch[1] || '';
|
|
291
|
-
const result = this.processCharacterMapping(lines, i, indent);
|
|
292
|
-
if (result.reconstructed) {
|
|
293
|
-
fixedLines.push(result.reconstructed);
|
|
294
|
-
i = result.nextIndex;
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (line) {
|
|
299
|
-
fixedLines.push(line);
|
|
300
|
-
}
|
|
301
|
-
i++;
|
|
302
|
-
}
|
|
303
|
-
return fixedLines.join('\n');
|
|
304
|
-
}
|
|
305
|
-
processCharacterMapping(lines, startIndex, indent) {
|
|
306
|
-
const charMappings = [];
|
|
307
|
-
let j = startIndex;
|
|
308
|
-
while (j < lines.length) {
|
|
309
|
-
const currentLine = lines[j];
|
|
310
|
-
if (!currentLine) {
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
const currentMatch = currentLine.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
314
|
-
if (currentMatch && currentMatch[1] === indent && currentMatch[2] && currentMatch[3]) {
|
|
315
|
-
const index = parseInt(currentMatch[2]);
|
|
316
|
-
let char = currentMatch[3];
|
|
317
|
-
if (char && char.startsWith('"') && char.endsWith('"')) {
|
|
318
|
-
char = char.slice(1, -1);
|
|
319
|
-
}
|
|
320
|
-
charMappings.push({ index, char });
|
|
321
|
-
j++;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (charMappings.length > 3) {
|
|
328
|
-
charMappings.sort((a, b) => a.index - b.index);
|
|
329
|
-
const reconstructed = charMappings.map((m) => m.char).join('');
|
|
330
|
-
if (reconstructed.includes('{{') && reconstructed.includes('}}')) {
|
|
331
|
-
return { reconstructed: `${indent}${reconstructed}`, nextIndex: j };
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return { nextIndex: j };
|
|
335
|
-
}
|
|
336
|
-
applyHelmTemplateReplacements(processed) {
|
|
337
|
-
processed = this.fixConditionalBlocks(processed);
|
|
338
|
-
processed = this.fixPipeExpressions(processed);
|
|
339
|
-
processed = this.fixNestedQuotes(processed);
|
|
340
|
-
processed = this.cleanupArtifacts(processed);
|
|
341
|
-
processed = this.removeHelmExpressionQuotes(processed);
|
|
342
|
-
return processed;
|
|
343
|
-
}
|
|
344
|
-
fixIncludeStatements(processed) {
|
|
345
|
-
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \| nindent (\d+) \}\}/g, '{{ include "$1" . | nindent $2 }}');
|
|
346
|
-
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \}\}/g, '{{ include "$1" . }}');
|
|
347
|
-
return processed;
|
|
348
|
-
}
|
|
349
|
-
fixFunctionCalls(processed) {
|
|
350
|
-
return processed.replace(/\{\{ (\w+) \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ $1 "$2" $3 }}');
|
|
351
|
-
}
|
|
352
|
-
fixChartReferences(processed) {
|
|
353
|
-
return processed.replace(/\{\{ \.Chart\.(\w+) \| replace \\"([^"]*)\\" \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ .Chart.$1 | replace "$2" "$3" $4 }}');
|
|
354
|
-
}
|
|
355
|
-
removeHelmExpressionQuotes(processed) {
|
|
356
|
-
const HELM_EXPRESSION_REPLACEMENT = '$1$2: {{$3}}';
|
|
357
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\|\s*(?:int|float|bool|number)[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
358
|
-
processed = processed.replace(/^(\s*)(port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
359
|
-
processed = processed.replace(/^(\s*)(port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
360
|
-
processed = processed.replace(/^(\s*)(service\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
361
|
-
processed = processed.replace(/^(\s*)(backend\.service\.port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
362
|
-
processed = processed.replace(/^(\s*)(spec\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
363
|
-
processed = processed.replace(/^(\s*)([^:\s]*\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
364
|
-
processed = processed.replace(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
365
|
-
processed = processed.replace(/^(\s*)(replicas):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
366
|
-
processed = processed.replace(/^(\s*)(targetPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
367
|
-
processed = processed.replace(/^(\s*)(nodePort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
368
|
-
processed = processed.replace(/^(\s*)(containerPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
369
|
-
processed = processed.replace(/^(\s*)(hostPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
370
|
-
processed = processed.replace(/^(\s*)(weight):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
371
|
-
processed = processed.replace(/^(\s*)(priority):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
372
|
-
processed = processed.replace(/^(\s*)(timeoutSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
373
|
-
processed = processed.replace(/^(\s*)(periodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
374
|
-
processed = processed.replace(/^(\s*)(successThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
375
|
-
processed = processed.replace(/^(\s*)(failureThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
376
|
-
processed = processed.replace(/^(\s*)(initialDelaySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
377
|
-
processed = processed.replace(/^(\s*)(terminationGracePeriodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
378
|
-
processed = processed.replace(/^(\s*)(activeDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
379
|
-
processed = processed.replace(/^(\s*)(backoffLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
380
|
-
processed = processed.replace(/^(\s*)(parallelism):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
381
|
-
processed = processed.replace(/^(\s*)(completions):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
382
|
-
processed = processed.replace(/^(\s*)(revisionHistoryLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
383
|
-
processed = processed.replace(/^(\s*)(progressDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
384
|
-
processed = processed.replace(/^(\s*)(minReadySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
385
|
-
processed = processed.replace(/^(\s*)(maxUnavailable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
386
|
-
processed = processed.replace(/^(\s*)(maxSurge):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
387
|
-
processed = processed.replace(/^(\s*)(cpu):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
388
|
-
processed = processed.replace(/^(\s*)(memory):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
389
|
-
processed = processed.replace(/^(\s*)(enabled):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
390
|
-
processed = processed.replace(/^(\s*)(create):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
391
|
-
processed = processed.replace(/^(\s*)(allow):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
392
|
-
processed = processed.replace(/^(\s*)(disable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
393
|
-
processed = processed.replace(/^(\s*)(force):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
394
|
-
processed = processed.replace(/^(\s*)(required):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
395
|
-
processed = processed.replace(/^(\s*)(optional):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
396
|
-
processed = processed.replace(/^(\s*)(readOnly):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
397
|
-
processed = processed.replace(/^(\s*)(runAsNonRoot):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
398
|
-
processed = processed.replace(/^(\s*)(privileged):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
399
|
-
processed = processed.replace(/^(\s*)(allowPrivilegeEscalation):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
400
|
-
processed = processed.replace(/^(\s*)(readOnlyRootFilesystem):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
401
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*true[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
402
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*false[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
403
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\d+[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
404
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\beq\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
405
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bne\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
406
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\blt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
407
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\ble\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
408
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bgt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
409
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bge\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
410
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\band\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
411
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bor\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
412
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bnot\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
413
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[+][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
414
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[-][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
415
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[*][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
416
|
-
processed = processed.replace(/:\s*"([^"]*\{\{[^}]+\}\}[^"]*)"/g, ': $1');
|
|
417
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(true|false)"$/gm, '$1$2: $3');
|
|
418
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+)"$/gm, '$1$2: $3');
|
|
419
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.\d+)"$/gm, '$1$2: $3');
|
|
420
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.?\d*[eE][+-]?\d+)"$/gm, '$1$2: $3');
|
|
421
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
422
|
-
processed = processed.replace(/"\{\{([^}]*)\}\}"/g, '{{$1}}');
|
|
423
|
-
processed = processed.replace(/^(\s*)-\s*"\{\{([^}]*)\}\}"/gm, '$1- {{$2}}');
|
|
424
|
-
return processed;
|
|
425
|
-
}
|
|
426
|
-
fixConditionalBlocks(processed) {
|
|
427
|
-
return processed.replace(/"\{\{-\s*(if|with|range)([^}]*)\}\}([^"]*)\{\{-\s*end\s*\}\}"/g, '{{- $1$2}}$3{{- end }}');
|
|
428
|
-
}
|
|
429
|
-
fixPipeExpressions(processed) {
|
|
430
|
-
return processed.replace(/"\{\{([^}]*\|[^}]*)\}\}"/g, '{{$1}}');
|
|
431
|
-
}
|
|
432
|
-
fixNestedQuotes(processed) {
|
|
433
|
-
return processed.replace(/\{\{([^}]*)\\"([^"]*)\\"([^}]*)\}\}/g, '{{$1"$2"$3}}');
|
|
434
|
-
}
|
|
435
|
-
cleanupArtifacts(processed) {
|
|
436
|
-
processed = processed.replace(/": "\.nan"/g, ': .nan');
|
|
437
|
-
processed = processed.replace(/: \{\{([^}]+)\}\}$/gm, ': {{$1}}');
|
|
438
|
-
return processed;
|
|
439
|
-
}
|
|
440
274
|
write(outDir) {
|
|
441
275
|
const timer = this.logger.time('chart_write');
|
|
442
276
|
this.logger.info('Starting chart write operation', {
|
|
@@ -107,10 +107,10 @@ export function createFlexibleSubchart(scope, id, config) {
|
|
|
107
107
|
}
|
|
108
108
|
export function generateFlexibleSubchartTemplate(name) {
|
|
109
109
|
return `import { App } from 'cdk8s';
|
|
110
|
-
import { Rutter } from 'timonel';
|
|
110
|
+
import { Rutter, helmInclude, helmIf, helmWith, createHelmExpression as helm } from 'timonel';
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Creates a new chart with
|
|
113
|
+
* Creates a new chart with type-safe Helm helpers
|
|
114
114
|
* @returns Rutter instance for Helm chart generation
|
|
115
115
|
* @since 2.11.0
|
|
116
116
|
*/
|
|
@@ -126,45 +126,43 @@ export default function createChart() {
|
|
|
126
126
|
scope: app,
|
|
127
127
|
defaultValues: {
|
|
128
128
|
appName: '${name}',
|
|
129
|
-
image:
|
|
129
|
+
image: {
|
|
130
|
+
repository: 'nginx',
|
|
131
|
+
tag: 'latest'
|
|
132
|
+
},
|
|
130
133
|
port: 80,
|
|
131
134
|
replicas: 1,
|
|
132
135
|
},
|
|
133
136
|
});
|
|
134
137
|
|
|
135
|
-
// Add
|
|
138
|
+
// Add Deployment with type-safe helpers
|
|
136
139
|
rutter.addManifest({
|
|
137
140
|
apiVersion: 'apps/v1',
|
|
138
141
|
kind: 'Deployment',
|
|
139
142
|
metadata: {
|
|
140
|
-
name: '
|
|
141
|
-
labels: {
|
|
142
|
-
'app.kubernetes.io/name': '${name}'
|
|
143
|
-
}
|
|
143
|
+
name: helmInclude('chart.fullname', '.'),
|
|
144
|
+
labels: helmInclude('chart.labels', '.', { pipe: 'nindent 4' })
|
|
144
145
|
},
|
|
145
146
|
spec: {
|
|
146
|
-
replicas: 1,
|
|
147
|
+
replicas: helmIf('.Values.autoscaling.enabled', '{{ .Values.replicas }}', '1'),
|
|
147
148
|
selector: {
|
|
148
|
-
matchLabels: {
|
|
149
|
-
'app.kubernetes.io/name': '${name}'
|
|
150
|
-
}
|
|
149
|
+
matchLabels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 6' })
|
|
151
150
|
},
|
|
152
151
|
template: {
|
|
153
152
|
metadata: {
|
|
154
|
-
labels: {
|
|
155
|
-
|
|
156
|
-
}
|
|
153
|
+
labels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 8' }),
|
|
154
|
+
annotations: helmWith('.Values.podAnnotations', helm('{{- toYaml . | nindent 8 }}'))
|
|
157
155
|
},
|
|
158
156
|
spec: {
|
|
159
157
|
containers: [{
|
|
160
158
|
name: '${name}',
|
|
161
|
-
image: '
|
|
159
|
+
image: helm('{{ .Values.image.repository }}:{{ .Values.image.tag }}'),
|
|
162
160
|
ports: [{
|
|
163
|
-
containerPort:
|
|
161
|
+
containerPort: helm('{{ .Values.port }}')
|
|
164
162
|
}],
|
|
165
163
|
env: [
|
|
166
|
-
{ name: 'APP_NAME', value: '
|
|
167
|
-
{ name: 'PORT', value: '
|
|
164
|
+
{ name: 'APP_NAME', value: helm('{{ .Values.appName }}') },
|
|
165
|
+
{ name: 'PORT', value: helm('{{ .Values.port | toString }}') }
|
|
168
166
|
]
|
|
169
167
|
}]
|
|
170
168
|
}
|
|
@@ -172,27 +170,23 @@ export default function createChart() {
|
|
|
172
170
|
}
|
|
173
171
|
}, 'deployment');
|
|
174
172
|
|
|
175
|
-
// Add Service
|
|
173
|
+
// Add Service with type-safe helpers
|
|
176
174
|
rutter.addManifest({
|
|
177
175
|
apiVersion: 'v1',
|
|
178
176
|
kind: 'Service',
|
|
179
177
|
metadata: {
|
|
180
|
-
name: '
|
|
181
|
-
labels: {
|
|
182
|
-
'app.kubernetes.io/name': '${name}'
|
|
183
|
-
}
|
|
178
|
+
name: helmInclude('chart.fullname', '.'),
|
|
179
|
+
labels: helmInclude('chart.labels', '.', { pipe: 'nindent 4' })
|
|
184
180
|
},
|
|
185
181
|
spec: {
|
|
186
182
|
type: 'ClusterIP',
|
|
187
183
|
ports: [{
|
|
188
|
-
port:
|
|
189
|
-
targetPort:
|
|
184
|
+
port: helm('{{ .Values.port }}'),
|
|
185
|
+
targetPort: helm('{{ .Values.port }}'),
|
|
190
186
|
protocol: 'TCP',
|
|
191
187
|
name: 'http'
|
|
192
188
|
}],
|
|
193
|
-
selector: {
|
|
194
|
-
'app.kubernetes.io/name': '${name}'
|
|
195
|
-
}
|
|
189
|
+
selector: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 4' })
|
|
196
190
|
}
|
|
197
191
|
}, 'service');
|
|
198
192
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync, copyFileSync, readdirSync, rmSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
4
|
import { App, Chart, ApiObject } from 'cdk8s';
|
|
5
5
|
import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
|
|
6
6
|
import { generateHelpersTemplate } from '../utils/helmHelpers.js';
|
|
7
7
|
import { createFlexibleSubchart } from './flexible-subchart.js';
|
|
8
8
|
export function generateUmbrellaChart(name) {
|
|
9
9
|
return `import { App } from 'cdk8s';
|
|
10
|
-
import { Rutter } from 'timonel';
|
|
10
|
+
import { Rutter, helmInclude, helmIf, helmWith, createHelmExpression as helm } from 'timonel';
|
|
11
11
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
|
-
import
|
|
13
|
+
import { parse, stringify } from 'yaml';
|
|
14
14
|
// Import subcharts - add your subchart imports here
|
|
15
15
|
|
|
16
16
|
type SynthMode = 'dependencies' | 'inline';
|
|
@@ -42,7 +42,7 @@ function readYamlFile(filePath: string): Record<string, unknown> {
|
|
|
42
42
|
if (!existsSync(filePath)) {
|
|
43
43
|
return {};
|
|
44
44
|
}
|
|
45
|
-
const content =
|
|
45
|
+
const content = parse(readFileSync(filePath, 'utf8'));
|
|
46
46
|
return content && typeof content === 'object' ? (content as Record<string, unknown>) : {};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -147,8 +147,8 @@ export function synth(outDir: string, options?: SynthOptions) {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
writeFileSync(chartPath,
|
|
151
|
-
writeFileSync(valuesPath,
|
|
150
|
+
writeFileSync(chartPath, stringify(chartDoc));
|
|
151
|
+
writeFileSync(valuesPath, stringify(valuesDoc));
|
|
152
152
|
|
|
153
153
|
app.synth();
|
|
154
154
|
|
|
@@ -327,7 +327,7 @@ export class UmbrellaChartTemplate extends Chart {
|
|
|
327
327
|
const assets = getAssets?.() || [];
|
|
328
328
|
assets.forEach((asset, assetIndex) => {
|
|
329
329
|
try {
|
|
330
|
-
const manifest =
|
|
330
|
+
const manifest = parse(asset.yaml);
|
|
331
331
|
if (manifest?.apiVersion && manifest?.kind) {
|
|
332
332
|
new ApiObject(flexibleSubchart, `${String(manifest.kind).toLowerCase()}-${assetIndex}`, {
|
|
333
333
|
apiVersion: String(manifest.apiVersion),
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface EnvVarConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
type: 'value' | 'secret';
|
|
4
|
+
scope?: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
secretName?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LoadEnvVarsOptions {
|
|
9
|
+
configPath?: string;
|
|
10
|
+
defaultScope?: string;
|
|
11
|
+
fallbackConfig?: EnvVarConfig[];
|
|
12
|
+
}
|
|
13
|
+
export declare function loadEnvVarsConfig(options?: LoadEnvVarsOptions): EnvVarConfig[];
|
|
14
|
+
export declare function generateEnvVars(config: EnvVarConfig[], options?: {
|
|
15
|
+
defaultScope?: string;
|
|
16
|
+
}): Array<Record<string, unknown>>;
|
|
17
|
+
export declare function loadAndGenerateEnvVars(options?: LoadEnvVarsOptions & {
|
|
18
|
+
defaultScope?: string;
|
|
19
|
+
}): Array<Record<string, unknown>>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { createHelmExpression } from './helmControlStructures.js';
|
|
5
|
+
export function loadEnvVarsConfig(options = {}) {
|
|
6
|
+
const { configPath, fallbackConfig = [] } = options;
|
|
7
|
+
try {
|
|
8
|
+
if (configPath && existsSync(configPath)) {
|
|
9
|
+
const data = readFileSync(configPath, 'utf-8');
|
|
10
|
+
return configPath.endsWith('.yaml') || configPath.endsWith('.yml')
|
|
11
|
+
? parseYaml(data)
|
|
12
|
+
: JSON.parse(data);
|
|
13
|
+
}
|
|
14
|
+
const yamlPath = join(process.cwd(), 'env-config.yaml');
|
|
15
|
+
if (existsSync(yamlPath)) {
|
|
16
|
+
const data = readFileSync(yamlPath, 'utf-8');
|
|
17
|
+
return parseYaml(data);
|
|
18
|
+
}
|
|
19
|
+
const jsonPath = join(process.cwd(), 'env-config.json');
|
|
20
|
+
if (existsSync(jsonPath)) {
|
|
21
|
+
const data = readFileSync(jsonPath, 'utf-8');
|
|
22
|
+
return JSON.parse(data);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
}
|
|
27
|
+
return fallbackConfig;
|
|
28
|
+
}
|
|
29
|
+
export function generateEnvVars(config, options = {}) {
|
|
30
|
+
const { defaultScope = 'global.env' } = options;
|
|
31
|
+
return config.map((item) => {
|
|
32
|
+
const scope = item.scope || defaultScope;
|
|
33
|
+
if (item.type === 'value') {
|
|
34
|
+
return {
|
|
35
|
+
name: item.name,
|
|
36
|
+
value: createHelmExpression(`{{ .Values.${scope}.${item.name} | default "${item.defaultValue || ''}" }}`),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return {
|
|
41
|
+
name: item.name,
|
|
42
|
+
valueFrom: {
|
|
43
|
+
secretKeyRef: {
|
|
44
|
+
name: item.secretName || createHelmExpression('{{ .Values.secretName }}'),
|
|
45
|
+
key: item.name,
|
|
46
|
+
optional: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function loadAndGenerateEnvVars(options = {}) {
|
|
54
|
+
const config = loadEnvVarsConfig(options);
|
|
55
|
+
const genOptions = options.defaultScope ? { defaultScope: options.defaultScope } : {};
|
|
56
|
+
return generateEnvVars(config, genOptions);
|
|
57
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isHelmConstruct } from './helmControlStructures.js';
|
|
2
|
+
export function serializeHelmConstruct(construct, indent = 0) {
|
|
3
|
+
const trimLeft = construct.options?.trimLeft ?? true;
|
|
4
|
+
const trimRight = construct.options?.trimRight ?? true;
|
|
5
|
+
const openTag = `{{${trimLeft ? '-' : ''} `;
|
|
6
|
+
const closeTag = ` ${trimRight ? '-' : ''}}}`;
|
|
7
|
+
switch (construct.type) {
|
|
8
|
+
case 'if': {
|
|
9
|
+
const data = construct.data;
|
|
10
|
+
let result = `${openTag}if ${data.condition}${closeTag}\n`;
|
|
11
|
+
result += serializeHelmContent(data.then, indent);
|
|
12
|
+
if (data.else !== undefined) {
|
|
13
|
+
result += `\n${openTag}else${closeTag}\n`;
|
|
14
|
+
result += serializeHelmContent(data.else, indent);
|
|
15
|
+
}
|
|
16
|
+
result += `\n${openTag}end${closeTag}`;
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unknown Helm construct type: ${construct.type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function serializeHelmContent(content, indent = 0) {
|
|
24
|
+
const indentStr = ' '.repeat(indent);
|
|
25
|
+
if (isHelmConstruct(content)) {
|
|
26
|
+
return serializeHelmConstruct(content, indent);
|
|
27
|
+
}
|
|
28
|
+
if (content === null || content === undefined) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
if (typeof content === 'string' || typeof content === 'number' || typeof content === 'boolean') {
|
|
32
|
+
return String(content);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(content)) {
|
|
35
|
+
return content
|
|
36
|
+
.map((item) => {
|
|
37
|
+
if (isHelmConstruct(item)) {
|
|
38
|
+
return serializeHelmConstruct(item, indent);
|
|
39
|
+
}
|
|
40
|
+
return serializeHelmContent(item, indent);
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
}
|
|
44
|
+
if (typeof content === 'object') {
|
|
45
|
+
const lines = [];
|
|
46
|
+
for (const [key, value] of Object.entries(content)) {
|
|
47
|
+
if (isHelmConstruct(value)) {
|
|
48
|
+
lines.push(`${indentStr}${key}: ${serializeHelmConstruct(value, indent + 1)}`);
|
|
49
|
+
}
|
|
50
|
+
else if (typeof value === 'object' && value !== null) {
|
|
51
|
+
lines.push(`${indentStr}${key}:`);
|
|
52
|
+
lines.push(serializeHelmContent(value, indent + 1));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push(`${indentStr}${key}: ${value}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface HelmConstruct {
|
|
2
|
+
__helmConstruct: true;
|
|
3
|
+
type: 'if' | 'range' | 'with' | 'include' | 'define' | 'var' | 'block' | 'comment' | 'fragment' | 'fieldConditional';
|
|
4
|
+
data: unknown;
|
|
5
|
+
options?: HelmWhitespaceOptions;
|
|
6
|
+
}
|
|
7
|
+
export declare function helmFragment(...contents: HelmContent[]): HelmConstruct;
|
|
8
|
+
export interface HelmExpression {
|
|
9
|
+
__helmExpression: true;
|
|
10
|
+
value: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createHelmExpression(value: string): HelmExpression;
|
|
13
|
+
export declare function isHelmExpression(value: unknown): value is HelmExpression;
|
|
14
|
+
export declare function helmRange(vars: string, collection: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
15
|
+
export declare function helmWith(scope: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
16
|
+
export declare function helmInclude(templateName: string, scope?: string, options?: {
|
|
17
|
+
pipe?: string;
|
|
18
|
+
} & HelmWhitespaceOptions): HelmConstruct;
|
|
19
|
+
export declare function helmDefine(name: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
20
|
+
export declare function helmVar(name: string, value: string, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
21
|
+
export declare function helmBlock(name: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
22
|
+
export declare function helmComment(text: string): HelmConstruct;
|
|
23
|
+
export type HelmContent = string | number | boolean | null | undefined | HelmConstruct | HelmExpression | {
|
|
24
|
+
[key: string]: HelmContent;
|
|
25
|
+
} | HelmContent[];
|
|
26
|
+
export interface HelmWhitespaceOptions {
|
|
27
|
+
trimLeft?: boolean;
|
|
28
|
+
trimRight?: boolean;
|
|
29
|
+
inline?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function helmIf(condition: string, thenContent: HelmContent, elseContent?: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
32
|
+
export declare function helmIfSimple(condition: string, thenContent: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
33
|
+
export declare function isHelmConstruct(value: unknown): value is HelmConstruct;
|