timonel 2.14.0-beta.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +371 -0
- package/README.md +202 -22
- package/SECURITY.md +25 -11
- package/dist/cli.js +54 -15
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lib/helm.js +28 -1
- package/dist/lib/helmChartWriter.js +32 -11
- package/dist/lib/resources/baseResourceProvider.js +5 -0
- package/dist/lib/resources/cloud/aws/awsResources.js +2 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.js +16 -2
- package/dist/lib/rutter.js +10 -8
- package/dist/lib/security.js +4 -4
- package/dist/lib/templates/flexible-subchart.js +14 -5
- package/dist/lib/templates/umbrella-chart.js +21 -10
- package/dist/lib/umbrellaRutter.js +19 -7
- package/dist/lib/utils/envVarsLoader.d.ts +19 -0
- package/dist/lib/utils/envVarsLoader.js +63 -0
- package/dist/lib/utils/helmControlStructures.d.ts +4 -5
- package/dist/lib/utils/helmControlStructures.js +12 -0
- package/dist/lib/utils/helmHelpers.js +10 -2
- package/dist/lib/utils/helmYamlSerializer.d.ts +2 -0
- package/dist/lib/utils/helmYamlSerializer.js +509 -23
- package/dist/lib/utils/logger.js +54 -39
- package/dist/lib/utils/valuesRef.d.ts +102 -0
- package/dist/lib/utils/valuesRef.js +273 -0
- package/package.json +1 -1
|
@@ -1,5 +1,34 @@
|
|
|
1
|
-
import { Document, Scalar, isMap, isScalar, visit } from 'yaml';
|
|
1
|
+
import { Document, Scalar, isMap, isScalar, visit, Pair } from 'yaml';
|
|
2
|
+
import { SecurityUtils } from '../security.js';
|
|
2
3
|
import { isHelmConstruct, isHelmExpression, createHelmExpression, } from './helmControlStructures.js';
|
|
4
|
+
import { isHelmValue, isHelmFieldConditional, isHelmRange, isHelmWith, } from './valuesRef.js';
|
|
5
|
+
const FIELD_CONDITIONAL = '__FIELD_CONDITIONAL__:';
|
|
6
|
+
const FIELD_WITH = '__FIELD_WITH__:';
|
|
7
|
+
const FIELD_WITH_MARKER = '__FIELD_WITH_MARKER__:';
|
|
8
|
+
function simpleHelmYaml(obj) {
|
|
9
|
+
function convertToPlain(value) {
|
|
10
|
+
if (isHelmValue(value)) {
|
|
11
|
+
return `{{ ${value.__path} }}`;
|
|
12
|
+
}
|
|
13
|
+
if (isHelmExpression(value)) {
|
|
14
|
+
return value.value;
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map(convertToPlain);
|
|
18
|
+
}
|
|
19
|
+
if (value !== null && typeof value === 'object') {
|
|
20
|
+
const result = {};
|
|
21
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22
|
+
result[k] = convertToPlain(v);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
const plain = convertToPlain(obj);
|
|
29
|
+
const doc = new Document(plain);
|
|
30
|
+
return doc.toString({ lineWidth: 0 });
|
|
31
|
+
}
|
|
3
32
|
function serializeElseIfChain(elseContent, openTag, closeTag) {
|
|
4
33
|
let result = '';
|
|
5
34
|
let currentElse = elseContent;
|
|
@@ -14,6 +43,9 @@ function serializeElseIfChain(elseContent, openTag, closeTag) {
|
|
|
14
43
|
return { result, remainingElse: currentElse };
|
|
15
44
|
}
|
|
16
45
|
function serializeHelmConstruct(construct) {
|
|
46
|
+
if (construct.type === 'fieldConditional') {
|
|
47
|
+
throw new Error('fieldConditional should not be serialized directly. It must be handled in preprocessHelmConstructs.');
|
|
48
|
+
}
|
|
17
49
|
const trimLeft = construct.options?.trimLeft ?? true;
|
|
18
50
|
const trimRight = construct.options?.trimRight ?? true;
|
|
19
51
|
const openTag = `{{${trimLeft ? '-' : ''} `;
|
|
@@ -21,22 +53,117 @@ function serializeHelmConstruct(construct) {
|
|
|
21
53
|
switch (construct.type) {
|
|
22
54
|
case 'if': {
|
|
23
55
|
const data = construct.data;
|
|
24
|
-
|
|
56
|
+
const isInline = construct.options?.inline ?? false;
|
|
57
|
+
const newline = isInline ? '' : '\n';
|
|
58
|
+
const space = isInline ? ' ' : '';
|
|
59
|
+
let result = `${openTag}if ${data.condition}${closeTag}${newline}`;
|
|
25
60
|
result += serializeHelmContent(data.then);
|
|
26
61
|
if (data.else !== undefined) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
62
|
+
if (isInline) {
|
|
63
|
+
result += `${space}${openTag}else${closeTag}${space}`;
|
|
64
|
+
result += serializeHelmContent(data.else);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const { result: elseIfResult, remainingElse } = serializeElseIfChain(data.else, openTag, closeTag);
|
|
68
|
+
result += elseIfResult;
|
|
69
|
+
if (remainingElse !== undefined) {
|
|
70
|
+
result += `\n${openTag}else${closeTag}\n`;
|
|
71
|
+
result += serializeHelmContent(remainingElse);
|
|
72
|
+
}
|
|
32
73
|
}
|
|
33
74
|
}
|
|
34
|
-
result +=
|
|
75
|
+
result += `${newline}${openTag}end${closeTag}`;
|
|
35
76
|
return result;
|
|
36
77
|
}
|
|
37
78
|
case 'fragment': {
|
|
38
79
|
const data = construct.data;
|
|
39
|
-
|
|
80
|
+
const strings = [];
|
|
81
|
+
const objects = [];
|
|
82
|
+
for (const item of data) {
|
|
83
|
+
if (typeof item === 'object' &&
|
|
84
|
+
item !== null &&
|
|
85
|
+
!Array.isArray(item) &&
|
|
86
|
+
!isHelmConstruct(item) &&
|
|
87
|
+
!isHelmExpression(item)) {
|
|
88
|
+
objects.push(item);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const serialized = serializeHelmContent(item);
|
|
92
|
+
if (serialized.trim().length > 0) {
|
|
93
|
+
strings.push(serialized);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (strings.length > 0 && objects.length > 0) {
|
|
98
|
+
const combinedObject = objects.reduce((acc, obj) => {
|
|
99
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
100
|
+
return { ...acc, ...obj };
|
|
101
|
+
}
|
|
102
|
+
return acc;
|
|
103
|
+
}, {});
|
|
104
|
+
const preprocessed = preprocessHelmConstructs(combinedObject);
|
|
105
|
+
const doc = new Document(preprocessed);
|
|
106
|
+
visit(doc, (_key, node) => {
|
|
107
|
+
if (isMap(node)) {
|
|
108
|
+
const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
|
|
109
|
+
pair.key.value === '__helmExpression' &&
|
|
110
|
+
isScalar(pair.value) &&
|
|
111
|
+
pair.value.value === true);
|
|
112
|
+
if (isHelmExpr) {
|
|
113
|
+
const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
|
|
114
|
+
if (valuePair && isScalar(valuePair.value)) {
|
|
115
|
+
const value = String(valuePair.value.value);
|
|
116
|
+
const scalar = new Scalar(value);
|
|
117
|
+
if (value.trim().startsWith('{{') && value.includes('\n')) {
|
|
118
|
+
scalar.type = 'BLOCK_LITERAL';
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
scalar.type = 'QUOTE_DOUBLE';
|
|
122
|
+
}
|
|
123
|
+
return scalar;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
});
|
|
129
|
+
let objectYaml = doc.toString({ lineWidth: 0 }).trim();
|
|
130
|
+
return [...strings, objectYaml].filter((s) => s.trim().length > 0).join('\n');
|
|
131
|
+
}
|
|
132
|
+
if (objects.length > 0) {
|
|
133
|
+
const combinedObject = objects.reduce((acc, obj) => {
|
|
134
|
+
if (typeof obj === 'object' && obj !== null) {
|
|
135
|
+
return { ...acc, ...obj };
|
|
136
|
+
}
|
|
137
|
+
return acc;
|
|
138
|
+
}, {});
|
|
139
|
+
const preprocessed = preprocessHelmConstructs(combinedObject);
|
|
140
|
+
const doc = new Document(preprocessed);
|
|
141
|
+
visit(doc, (_key, node) => {
|
|
142
|
+
if (isMap(node)) {
|
|
143
|
+
const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
|
|
144
|
+
pair.key.value === '__helmExpression' &&
|
|
145
|
+
isScalar(pair.value) &&
|
|
146
|
+
pair.value.value === true);
|
|
147
|
+
if (isHelmExpr) {
|
|
148
|
+
const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
|
|
149
|
+
if (valuePair && isScalar(valuePair.value)) {
|
|
150
|
+
const value = String(valuePair.value.value);
|
|
151
|
+
const scalar = new Scalar(value);
|
|
152
|
+
if (value.trim().startsWith('{{') && value.includes('\n')) {
|
|
153
|
+
scalar.type = 'BLOCK_LITERAL';
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
scalar.type = 'QUOTE_DOUBLE';
|
|
157
|
+
}
|
|
158
|
+
return scalar;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
});
|
|
164
|
+
return doc.toString({ lineWidth: 0 }).trim();
|
|
165
|
+
}
|
|
166
|
+
return strings.filter((s) => s.trim().length > 0).join('\n');
|
|
40
167
|
}
|
|
41
168
|
case 'range': {
|
|
42
169
|
const data = construct.data;
|
|
@@ -58,7 +185,7 @@ function serializeHelmConstruct(construct) {
|
|
|
58
185
|
if (data.pipe) {
|
|
59
186
|
result += ` | ${data.pipe}`;
|
|
60
187
|
}
|
|
61
|
-
result +=
|
|
188
|
+
result += closeTag;
|
|
62
189
|
return result;
|
|
63
190
|
}
|
|
64
191
|
case 'define': {
|
|
@@ -104,15 +231,47 @@ function serializeHelmContent(content) {
|
|
|
104
231
|
return String(content);
|
|
105
232
|
}
|
|
106
233
|
if (typeof content === 'object') {
|
|
107
|
-
const
|
|
234
|
+
const preprocessed = preprocessHelmConstructs(content);
|
|
235
|
+
const doc = new Document(preprocessed);
|
|
108
236
|
let yaml = doc.toString({ lineWidth: 0 });
|
|
109
237
|
yaml = yaml.trim();
|
|
110
238
|
return yaml;
|
|
111
239
|
}
|
|
112
240
|
return '';
|
|
113
241
|
}
|
|
114
|
-
function preprocessHelmConstructs(obj) {
|
|
242
|
+
export function preprocessHelmConstructs(obj) {
|
|
243
|
+
if (isHelmValue(obj)) {
|
|
244
|
+
const value = obj;
|
|
245
|
+
return createHelmExpression(`{{ ${value.__path} }}`);
|
|
246
|
+
}
|
|
247
|
+
if (isHelmFieldConditional(obj)) {
|
|
248
|
+
return obj;
|
|
249
|
+
}
|
|
250
|
+
if (isHelmRange(obj)) {
|
|
251
|
+
const range = obj;
|
|
252
|
+
const sourcePath = range.source.__path;
|
|
253
|
+
const itemProxy = { __path: '$item' };
|
|
254
|
+
const indexProxy = { __path: '$index' };
|
|
255
|
+
const content = range.callback(itemProxy, indexProxy);
|
|
256
|
+
const processedContent = preprocessHelmConstructs(content);
|
|
257
|
+
const contentDoc = new Document(processedContent);
|
|
258
|
+
const contentStr = contentDoc.toString({ lineWidth: 0 }).trim();
|
|
259
|
+
return createHelmExpression(`{{- range ${sourcePath} }}\n${contentStr}\n{{- end }}`);
|
|
260
|
+
}
|
|
261
|
+
if (isHelmWith(obj)) {
|
|
262
|
+
return obj;
|
|
263
|
+
}
|
|
115
264
|
if (isHelmConstruct(obj)) {
|
|
265
|
+
if (obj.type === 'fieldConditional') {
|
|
266
|
+
return obj;
|
|
267
|
+
}
|
|
268
|
+
if (obj.type === 'if') {
|
|
269
|
+
const ifData = obj.data;
|
|
270
|
+
const isInline = obj.options?.inline ?? false;
|
|
271
|
+
if (!isInline && (ifData.else === undefined || !('else' in ifData))) {
|
|
272
|
+
return obj;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
116
275
|
const helmTemplate = serializeHelmConstruct(obj);
|
|
117
276
|
return createHelmExpression(helmTemplate);
|
|
118
277
|
}
|
|
@@ -125,6 +284,111 @@ function preprocessHelmConstructs(obj) {
|
|
|
125
284
|
}
|
|
126
285
|
const result = {};
|
|
127
286
|
for (const [key, value] of Object.entries(obj)) {
|
|
287
|
+
if (isHelmWith(value)) {
|
|
288
|
+
const withBlock = value;
|
|
289
|
+
const source = withBlock.source;
|
|
290
|
+
const sourcePath = source?.__path;
|
|
291
|
+
if (!sourcePath) {
|
|
292
|
+
throw new Error('HelmWith source must have a valid __path');
|
|
293
|
+
}
|
|
294
|
+
const ctxProxy = { __path: '.' };
|
|
295
|
+
const content = withBlock.callback(ctxProxy);
|
|
296
|
+
let contentStr;
|
|
297
|
+
if (isHelmExpression(content)) {
|
|
298
|
+
contentStr = content.value;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const processedContent = preprocessHelmConstructs(content);
|
|
302
|
+
const contentDoc = new Document(processedContent);
|
|
303
|
+
contentStr = contentDoc.toString({ lineWidth: 0 }).trim();
|
|
304
|
+
}
|
|
305
|
+
const template = `{{- with ${sourcePath} }}\n${key}:\n ${contentStr}\n{{- end }}`;
|
|
306
|
+
const marker = `__FIELD_WITH__:${key}:${template}`;
|
|
307
|
+
result[`__fieldWithTemplate_${key}`] = createHelmExpression(marker);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (isHelmConstruct(value) && value.type === 'if') {
|
|
311
|
+
const ifData = value.data;
|
|
312
|
+
const isInline = value.options?.inline ?? false;
|
|
313
|
+
if (!isInline && (ifData.else === undefined || !('else' in ifData))) {
|
|
314
|
+
const preprocessedThen = preprocessHelmConstructs(ifData.then);
|
|
315
|
+
let thenValueStr = '';
|
|
316
|
+
let isMultilineObject = false;
|
|
317
|
+
if (isHelmExpression(preprocessedThen)) {
|
|
318
|
+
thenValueStr = preprocessedThen.value.trim();
|
|
319
|
+
isMultilineObject = thenValueStr.includes('\n');
|
|
320
|
+
}
|
|
321
|
+
else if (typeof preprocessedThen === 'string') {
|
|
322
|
+
thenValueStr = preprocessedThen.trim();
|
|
323
|
+
isMultilineObject = thenValueStr.includes('\n');
|
|
324
|
+
}
|
|
325
|
+
else if (Array.isArray(preprocessedThen)) {
|
|
326
|
+
const tempDoc = new Document(preprocessedThen);
|
|
327
|
+
thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
|
|
328
|
+
isMultilineObject = true;
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
const tempDoc = new Document(preprocessedThen);
|
|
332
|
+
thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
|
|
333
|
+
isMultilineObject = thenValueStr.includes('\n');
|
|
334
|
+
}
|
|
335
|
+
const constructOptions = isHelmConstruct(value) ? value.options : undefined;
|
|
336
|
+
const trimLeft = constructOptions?.trimLeft ?? true;
|
|
337
|
+
const openTag = `{{${trimLeft ? '-' : ''} `;
|
|
338
|
+
const closeTag = ` ${(constructOptions?.trimRight ?? true) ? '-' : ''}}}`;
|
|
339
|
+
let formattedContent;
|
|
340
|
+
if (isMultilineObject) {
|
|
341
|
+
const indentedValue = thenValueStr
|
|
342
|
+
.split('\n')
|
|
343
|
+
.map((line) => ' ' + line)
|
|
344
|
+
.join('\n');
|
|
345
|
+
formattedContent = `${key}:\n${indentedValue}`;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
formattedContent = `${key}: ${thenValueStr}`;
|
|
349
|
+
}
|
|
350
|
+
const conditionalTemplate = `${openTag}if ${ifData.condition}${closeTag}\n ${formattedContent}\n${openTag}end${closeTag}`;
|
|
351
|
+
result[key] = createHelmExpression(`__FIELD_CONDITIONAL__:${key}:${conditionalTemplate}`);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (isHelmFieldConditional(value)) {
|
|
356
|
+
const fieldCond = value;
|
|
357
|
+
const condition = fieldCond.condition.__condition;
|
|
358
|
+
const thenValueStr = simpleHelmYaml(fieldCond.thenValue).trim();
|
|
359
|
+
const isMultilineObject = thenValueStr.includes('\n');
|
|
360
|
+
const startsWithDash = thenValueStr.trimStart().startsWith('-');
|
|
361
|
+
let formattedContent;
|
|
362
|
+
if (isMultilineObject || startsWithDash) {
|
|
363
|
+
const lines = thenValueStr.split('\n');
|
|
364
|
+
const indentedValue = lines.map((line) => ' ' + line).join('\n');
|
|
365
|
+
formattedContent = `${key}:\n${indentedValue}`;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
formattedContent = `${key}: ${thenValueStr}`;
|
|
369
|
+
}
|
|
370
|
+
const conditionalTemplate = `{{- if ${condition} }}\n${formattedContent}\n{{- end }}`;
|
|
371
|
+
result[key] = createHelmExpression(`__FIELD_CONDITIONAL__:${key}:${conditionalTemplate}`);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (isHelmConstruct(value) && value.type === 'fieldConditional') {
|
|
375
|
+
const fieldData = value.data;
|
|
376
|
+
const preprocessedThen = preprocessHelmConstructs(fieldData.then);
|
|
377
|
+
let thenValueStr = '';
|
|
378
|
+
if (isHelmExpression(preprocessedThen)) {
|
|
379
|
+
thenValueStr = preprocessedThen.value;
|
|
380
|
+
}
|
|
381
|
+
else if (typeof preprocessedThen === 'string') {
|
|
382
|
+
thenValueStr = preprocessedThen;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
const tempDoc = new Document(preprocessedThen);
|
|
386
|
+
thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
|
|
387
|
+
}
|
|
388
|
+
const conditionalTemplate = `{{- if ${fieldData.condition} }}\n ${fieldData.fieldKey}: ${thenValueStr}\n{{- end }}`;
|
|
389
|
+
result[`__fieldConditionalTemplate_${fieldData.fieldKey}`] = conditionalTemplate;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
128
392
|
result[key] = preprocessHelmConstructs(value);
|
|
129
393
|
}
|
|
130
394
|
return result;
|
|
@@ -144,10 +408,22 @@ export function dumpHelmAwareYaml(obj, options = {}) {
|
|
|
144
408
|
const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
|
|
145
409
|
if (valuePair && isScalar(valuePair.value)) {
|
|
146
410
|
const value = String(valuePair.value.value);
|
|
411
|
+
if (value.startsWith(FIELD_CONDITIONAL) ||
|
|
412
|
+
value.startsWith(FIELD_WITH) ||
|
|
413
|
+
value.startsWith(FIELD_WITH_MARKER)) {
|
|
414
|
+
const scalar = new Scalar(value);
|
|
415
|
+
scalar.type = 'BLOCK_LITERAL';
|
|
416
|
+
return scalar;
|
|
417
|
+
}
|
|
147
418
|
const scalar = new Scalar(value);
|
|
148
|
-
|
|
419
|
+
const isMultilineTemplate = value.includes('\n') && value.trim().startsWith('{{');
|
|
420
|
+
const isWithOrRange = value.trim().startsWith('{{- with ') || value.trim().startsWith('{{- range ');
|
|
421
|
+
if (isMultilineTemplate && !isWithOrRange) {
|
|
149
422
|
scalar.type = 'BLOCK_LITERAL';
|
|
150
423
|
}
|
|
424
|
+
else if (isWithOrRange) {
|
|
425
|
+
scalar.type = 'PLAIN';
|
|
426
|
+
}
|
|
151
427
|
else {
|
|
152
428
|
scalar.type = 'QUOTE_DOUBLE';
|
|
153
429
|
}
|
|
@@ -157,14 +433,216 @@ export function dumpHelmAwareYaml(obj, options = {}) {
|
|
|
157
433
|
}
|
|
158
434
|
return undefined;
|
|
159
435
|
});
|
|
160
|
-
const toStringOptions = {};
|
|
436
|
+
const toStringOptions = { lineWidth: 0 };
|
|
161
437
|
if (options.lineWidth !== undefined) {
|
|
162
438
|
toStringOptions.lineWidth = options.lineWidth;
|
|
163
439
|
}
|
|
440
|
+
const templatePairs = [];
|
|
441
|
+
visit(doc, (_key, node) => {
|
|
442
|
+
if (isMap(node)) {
|
|
443
|
+
for (let i = 0; i < node.items.length; i++) {
|
|
444
|
+
const pair = node.items[i];
|
|
445
|
+
if (pair && isScalar(pair.key)) {
|
|
446
|
+
const keyStr = String(pair.key.value);
|
|
447
|
+
if (keyStr.startsWith('__fieldConditionalTemplate_') && isScalar(pair.value)) {
|
|
448
|
+
const template = String(pair.value.value);
|
|
449
|
+
const fieldKey = keyStr.replace('__fieldConditionalTemplate_', '');
|
|
450
|
+
templatePairs.push({ parent: node, index: i, template, fieldKey });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return undefined;
|
|
456
|
+
});
|
|
457
|
+
for (let i = templatePairs.length - 1; i >= 0; i--) {
|
|
458
|
+
const templatePair = templatePairs[i];
|
|
459
|
+
if (!templatePair)
|
|
460
|
+
continue;
|
|
461
|
+
const { parent, index, template } = templatePair;
|
|
462
|
+
parent.items.splice(index, 1);
|
|
463
|
+
const tempDoc = new Document({ __temp: template });
|
|
464
|
+
const _tempYaml = tempDoc.toString({ lineWidth: 0 });
|
|
465
|
+
const templateScalar = new Scalar(template);
|
|
466
|
+
templateScalar.type = 'BLOCK_LITERAL';
|
|
467
|
+
const specialKeyScalar = new Scalar('__fieldConditionalContent');
|
|
468
|
+
const templatePairObj = new Pair(specialKeyScalar, templateScalar);
|
|
469
|
+
parent.items.splice(index, 0, templatePairObj);
|
|
470
|
+
}
|
|
164
471
|
let result = doc.toString(toStringOptions);
|
|
472
|
+
while (result.includes('__fieldConditionalContent:')) {
|
|
473
|
+
const markerIndex = result.indexOf('__fieldConditionalContent:');
|
|
474
|
+
if (markerIndex === -1)
|
|
475
|
+
break;
|
|
476
|
+
const lineStart = result.lastIndexOf('\n', markerIndex);
|
|
477
|
+
const lineBeforeMarker = result.substring(lineStart + 1, markerIndex);
|
|
478
|
+
const indentMatch = lineBeforeMarker.match(/^(\s*)/);
|
|
479
|
+
const baseIndent = indentMatch && indentMatch[1] ? indentMatch[1] : '';
|
|
480
|
+
const afterMarker = result.substring(markerIndex);
|
|
481
|
+
let pipeLineMatch = afterMarker.match(/^__fieldConditionalContent:\s*(\|-?)\s*\n(\s*)/);
|
|
482
|
+
let pipeOnSameLine = true;
|
|
483
|
+
if (!pipeLineMatch) {
|
|
484
|
+
pipeLineMatch = afterMarker.match(/^__fieldConditionalContent:\s*\n(\s*)(\|-?)\s*\n/);
|
|
485
|
+
pipeOnSameLine = false;
|
|
486
|
+
}
|
|
487
|
+
if (!pipeLineMatch)
|
|
488
|
+
break;
|
|
489
|
+
const pipeChar = pipeOnSameLine ? pipeLineMatch[1] : pipeLineMatch[2];
|
|
490
|
+
const pipeIndent = pipeOnSameLine ? pipeLineMatch[2] || '' : pipeLineMatch[1] || '';
|
|
491
|
+
if (!pipeChar)
|
|
492
|
+
break;
|
|
493
|
+
const pipeIndex = markerIndex + pipeLineMatch[0].length;
|
|
494
|
+
const afterPipe = result.substring(pipeIndex);
|
|
495
|
+
const templateStartMatch = afterPipe.match(/^\n(\s*)(\{\{-)/);
|
|
496
|
+
if (!templateStartMatch) {
|
|
497
|
+
const templateStartMatchNoNewline = afterPipe.match(/^(\s*)(\{\{-)/);
|
|
498
|
+
if (!templateStartMatchNoNewline)
|
|
499
|
+
break;
|
|
500
|
+
const _templateIndent = templateStartMatchNoNewline[1] || '';
|
|
501
|
+
const templateStart = templateStartMatchNoNewline[2];
|
|
502
|
+
if (!templateStart)
|
|
503
|
+
break;
|
|
504
|
+
const templateStartIndex = pipeIndex + templateStartMatchNoNewline[0].length;
|
|
505
|
+
const templateSection = result.substring(templateStartIndex);
|
|
506
|
+
const endMatch = templateSection.match(/(\{\{-?\s*end\s*-?\}\})/);
|
|
507
|
+
if (!endMatch)
|
|
508
|
+
break;
|
|
509
|
+
const endIndex = endMatch.index;
|
|
510
|
+
if (endIndex === undefined)
|
|
511
|
+
break;
|
|
512
|
+
const templateEndIndex = templateStartIndex + endIndex + endMatch[0].length;
|
|
513
|
+
const templateContent = result.substring(templateStartIndex, templateEndIndex);
|
|
514
|
+
const beforeMarker = result.substring(0, lineStart + 1);
|
|
515
|
+
const afterTemplate = result.substring(templateEndIndex);
|
|
516
|
+
const pipeIndentEscaped = pipeIndent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
517
|
+
const adjustedTemplate = templateContent.replace(new RegExp(`^${pipeIndentEscaped}`, 'gm'), baseIndent);
|
|
518
|
+
result = beforeMarker + adjustedTemplate + afterTemplate;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
const _templateIndent = templateStartMatch[1] || '';
|
|
522
|
+
const templateStart = templateStartMatch[2];
|
|
523
|
+
if (!templateStart)
|
|
524
|
+
break;
|
|
525
|
+
const templateStartIndex = pipeIndex + templateStartMatch[0].length;
|
|
526
|
+
const templateSection = result.substring(templateStartIndex);
|
|
527
|
+
const endMatch = templateSection.match(/(\{\{-?\s*end\s*-?\}\})/);
|
|
528
|
+
if (!endMatch)
|
|
529
|
+
break;
|
|
530
|
+
const endIndex = endMatch.index;
|
|
531
|
+
if (endIndex === undefined)
|
|
532
|
+
break;
|
|
533
|
+
const templateEndIndex = templateStartIndex + endIndex + endMatch[0].length;
|
|
534
|
+
const templateContent = result.substring(templateStartIndex, templateEndIndex);
|
|
535
|
+
const beforeMarker = result.substring(0, lineStart + 1);
|
|
536
|
+
const afterTemplate = result.substring(templateEndIndex);
|
|
537
|
+
const pipeIndentEscaped = pipeIndent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
538
|
+
const adjustedTemplate = templateContent.replace(new RegExp(`^${pipeIndentEscaped}`, 'gm'), baseIndent);
|
|
539
|
+
result = beforeMarker + adjustedTemplate + afterTemplate;
|
|
540
|
+
}
|
|
165
541
|
result = result.replace(/\\"/g, '"');
|
|
166
|
-
result = result.replace(
|
|
167
|
-
|
|
542
|
+
result = result.replace(/^(\s+)(\w+):\s+(\{\{-?\s*if\s+[^}]+\}\})\s*\n(\s+)(.+?)\n(\s+)(\{\{-?\s*end\s*-?\}\})/gm, (match, baseIndent, fieldKey, ifTag, contentIndent, content, endIndent, endTag) => {
|
|
543
|
+
const contentLines = content.split('\n');
|
|
544
|
+
const firstContentLine = contentLines[0]?.trim() || '';
|
|
545
|
+
if (firstContentLine.startsWith(`${fieldKey}:`)) {
|
|
546
|
+
const escapedFieldKey = fieldKey.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
547
|
+
contentLines[0] = contentLines[0].replace(new RegExp(`^\\s*${escapedFieldKey}:\\s*`), '');
|
|
548
|
+
content = contentLines.join('\n');
|
|
549
|
+
}
|
|
550
|
+
const contentFormatted = content
|
|
551
|
+
? '\n' +
|
|
552
|
+
content
|
|
553
|
+
.split('\n')
|
|
554
|
+
.map((line) => baseIndent + ' ' + line.trimStart())
|
|
555
|
+
.join('\n')
|
|
556
|
+
: '';
|
|
557
|
+
return `${baseIndent}${ifTag}\n${baseIndent} ${fieldKey}:${contentFormatted}\n${baseIndent}${endTag}`;
|
|
558
|
+
});
|
|
559
|
+
result = postProcessFieldConditionals(result);
|
|
560
|
+
return result;
|
|
561
|
+
}
|
|
562
|
+
export function postProcessFieldConditionals(yaml) {
|
|
563
|
+
let result = yaml;
|
|
564
|
+
while (result.includes('__FIELD_WITH_MARKER__:')) {
|
|
565
|
+
const markerIndex = result.indexOf('__FIELD_WITH_MARKER__:');
|
|
566
|
+
if (markerIndex === -1)
|
|
567
|
+
break;
|
|
568
|
+
const beforeMarker = result.substring(0, markerIndex);
|
|
569
|
+
const lastNewline = beforeMarker.lastIndexOf('\n');
|
|
570
|
+
const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
|
|
571
|
+
const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
|
|
572
|
+
const fieldKeyMatch = fieldKeyLine.match(/^(\s*)(\w+):\s*|[-]?\s*$/);
|
|
573
|
+
if (!fieldKeyMatch || !fieldKeyMatch[2])
|
|
574
|
+
break;
|
|
575
|
+
const baseIndent = fieldKeyMatch[1] || '';
|
|
576
|
+
const fieldKey = fieldKeyMatch[2];
|
|
577
|
+
const afterMarker = result.substring(markerIndex);
|
|
578
|
+
const markerMatch = afterMarker.match(/^__FIELD_WITH_MARKER__:([^:]+):(.+?)$/m);
|
|
579
|
+
if (!markerMatch || !markerMatch[1] || !markerMatch[2])
|
|
580
|
+
break;
|
|
581
|
+
const path = markerMatch[1];
|
|
582
|
+
const content = markerMatch[2].trim();
|
|
583
|
+
const markerEnd = markerIndex + markerMatch[0].length;
|
|
584
|
+
const template = `${baseIndent}{{- with ${path} }}\n${baseIndent}${fieldKey}:\n${baseIndent} ${content}\n${baseIndent}{{- end }}`;
|
|
585
|
+
result = result.substring(0, secondLastNewline + 1) + template + result.substring(markerEnd);
|
|
586
|
+
}
|
|
587
|
+
while (result.includes('__FIELD_WITH__:')) {
|
|
588
|
+
const markerIndex = result.indexOf('__FIELD_WITH__:');
|
|
589
|
+
if (markerIndex === -1)
|
|
590
|
+
break;
|
|
591
|
+
const beforeMarker = result.substring(0, markerIndex);
|
|
592
|
+
const lastNewline = beforeMarker.lastIndexOf('\n');
|
|
593
|
+
const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
|
|
594
|
+
const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
|
|
595
|
+
const fieldKeyMatch = fieldKeyLine.match(/^(\s*)__fieldWithTemplate_\w+:\s*|[-]?\s*$/);
|
|
596
|
+
if (!fieldKeyMatch)
|
|
597
|
+
break;
|
|
598
|
+
const baseIndent = fieldKeyMatch[1];
|
|
599
|
+
const afterMarker = result.substring(markerIndex);
|
|
600
|
+
const markerMatch = afterMarker.match(/^__FIELD_WITH__:\w+:(.+?\{\{-?\s*end\s*-?\}\})/s);
|
|
601
|
+
if (!markerMatch || !markerMatch[1])
|
|
602
|
+
break;
|
|
603
|
+
const template = markerMatch[1];
|
|
604
|
+
const markerEnd = markerIndex + markerMatch[0].length;
|
|
605
|
+
const lines = template.split('\n');
|
|
606
|
+
const processed = lines
|
|
607
|
+
.map((line) => {
|
|
608
|
+
if (!line.trim())
|
|
609
|
+
return '';
|
|
610
|
+
const trimmed = line.trimStart();
|
|
611
|
+
return baseIndent + trimmed;
|
|
612
|
+
})
|
|
613
|
+
.join('\n');
|
|
614
|
+
result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
|
|
615
|
+
}
|
|
616
|
+
while (result.includes('__FIELD_CONDITIONAL__:')) {
|
|
617
|
+
const markerIndex = result.indexOf('__FIELD_CONDITIONAL__:');
|
|
618
|
+
if (markerIndex === -1)
|
|
619
|
+
break;
|
|
620
|
+
const beforeMarker = result.substring(0, markerIndex);
|
|
621
|
+
const lastNewline = beforeMarker.lastIndexOf('\n');
|
|
622
|
+
const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
|
|
623
|
+
const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
|
|
624
|
+
const fieldKeyMatch = fieldKeyLine.match(/^(\s*)(\w+):\s*|[-]?\s*$/);
|
|
625
|
+
if (!fieldKeyMatch)
|
|
626
|
+
break;
|
|
627
|
+
const baseIndent = fieldKeyMatch[1];
|
|
628
|
+
const afterMarker = result.substring(markerIndex);
|
|
629
|
+
const markerMatch = afterMarker.match(/^__FIELD_CONDITIONAL__:\w+:(.+?\{\{-?\s*end\s*-?\}\})/s);
|
|
630
|
+
if (!markerMatch || !markerMatch[1])
|
|
631
|
+
break;
|
|
632
|
+
const template = markerMatch[1];
|
|
633
|
+
const markerEnd = markerIndex + markerMatch[0].length;
|
|
634
|
+
const lines = template.split('\n');
|
|
635
|
+
const processed = lines
|
|
636
|
+
.map((line) => {
|
|
637
|
+
if (!line.trim())
|
|
638
|
+
return '';
|
|
639
|
+
const trimmed = line.trimStart();
|
|
640
|
+
return baseIndent + trimmed;
|
|
641
|
+
})
|
|
642
|
+
.join('\n');
|
|
643
|
+
result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
|
|
644
|
+
}
|
|
645
|
+
result = result.replace(/:\s*\|[-+]?\s*\n([ \t]*\{\{)/g, ':\n$1');
|
|
168
646
|
result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
|
|
169
647
|
return result;
|
|
170
648
|
}
|
|
@@ -242,13 +720,14 @@ export function parseHelmExpressions(content) {
|
|
|
242
720
|
let match;
|
|
243
721
|
regex.lastIndex = 0;
|
|
244
722
|
while ((match = regex.exec(line)) !== null) {
|
|
723
|
+
const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
|
|
245
724
|
expressions.push({
|
|
246
725
|
type,
|
|
247
|
-
expression:
|
|
726
|
+
expression: sanitizedExpression,
|
|
248
727
|
startLine: i + 1,
|
|
249
728
|
startCol: match.index + 1,
|
|
250
729
|
endLine: i + 1,
|
|
251
|
-
endCol: match.index + match[0]
|
|
730
|
+
endCol: match.index + (match[0]?.length || 0) + 1,
|
|
252
731
|
});
|
|
253
732
|
if (match.index === regex.lastIndex)
|
|
254
733
|
regex.lastIndex++;
|
|
@@ -304,14 +783,19 @@ function validateFunctionCalls(_content) {
|
|
|
304
783
|
function checkCommonIssues(yaml, warnings) {
|
|
305
784
|
const deprecatedFunctions = ['template'];
|
|
306
785
|
for (const func of deprecatedFunctions) {
|
|
307
|
-
|
|
786
|
+
if (!/^[a-zA-Z0-9_]+$/.test(func)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const escapedFunc = func.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
790
|
+
const pattern = new RegExp(`\\{\\{[^}]*\\b${escapedFunc}\\b[^}]*\\}\\}`, 'g');
|
|
308
791
|
let match;
|
|
309
792
|
while ((match = pattern.exec(yaml)) !== null) {
|
|
793
|
+
const sanitizedFunc = SecurityUtils.sanitizeLogMessage(func);
|
|
310
794
|
warnings.push({
|
|
311
795
|
type: 'semantic',
|
|
312
|
-
message: `Function '${
|
|
796
|
+
message: `Function '${sanitizedFunc}' is deprecated`,
|
|
313
797
|
expression: match[0],
|
|
314
|
-
suggestion: `Consider avoiding deprecated function '${
|
|
798
|
+
suggestion: `Consider avoiding deprecated function '${sanitizedFunc}'`,
|
|
315
799
|
});
|
|
316
800
|
}
|
|
317
801
|
}
|
|
@@ -322,11 +806,13 @@ function checkQuotedExpressions(yaml, warnings) {
|
|
|
322
806
|
let match;
|
|
323
807
|
pattern.lastIndex = 0;
|
|
324
808
|
while ((match = pattern.exec(yaml)) !== null) {
|
|
809
|
+
const sanitizedMatch = SecurityUtils.sanitizeLogMessage(match[1] || '');
|
|
810
|
+
const sanitizedExpression = SecurityUtils.sanitizeLogMessage(match[0] || '');
|
|
325
811
|
warnings.push({
|
|
326
812
|
type: 'semantic',
|
|
327
813
|
message: 'Helm expression should not be quoted',
|
|
328
|
-
expression:
|
|
329
|
-
suggestion: `Remove quotes around: ${
|
|
814
|
+
expression: sanitizedExpression,
|
|
815
|
+
suggestion: `Remove quotes around: ${sanitizedMatch}`,
|
|
330
816
|
});
|
|
331
817
|
}
|
|
332
818
|
}
|