timonel 2.8.3 → 2.9.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 +13 -0
- package/README.md +35 -0
- package/dist/cli.js +255 -680
- package/dist/index.d.ts +4 -1
- package/dist/index.js +4 -1
- package/dist/lib/helmChartWriter.js +26 -4
- package/dist/lib/rutter.d.ts +28 -16
- package/dist/lib/rutter.js +250 -48
- package/dist/lib/templates/basic-chart.d.ts +19 -0
- package/dist/lib/templates/basic-chart.js +189 -0
- package/dist/lib/templates/subchart.d.ts +27 -0
- package/dist/lib/templates/subchart.js +221 -0
- package/dist/lib/templates/umbrella-chart.d.ts +13 -0
- package/dist/lib/templates/umbrella-chart.js +222 -0
- package/dist/lib/types.d.ts +20 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils/helmHelpers.d.ts +18 -2
- package/dist/lib/utils/helmHelpers.js +246 -3
- package/package.json +4 -5
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ export * from './lib/helmChartWriter.js';
|
|
|
3
3
|
export * from './lib/rutter.js';
|
|
4
4
|
export * from './lib/security.js';
|
|
5
5
|
export * from './lib/umbrella.js';
|
|
6
|
+
export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
|
|
7
|
+
export { Subchart } from './lib/templates/subchart.js';
|
|
8
|
+
export { BasicChart } from './lib/templates/basic-chart.js';
|
|
6
9
|
export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './lib/resources/cloud/aws/awsResources.js';
|
|
7
10
|
export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec, } from './lib/resources/cloud/aws/karpenterResources.js';
|
|
8
11
|
export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
|
|
9
|
-
export { AWS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, STANDARD_HELPERS, type HelperDefinition, } from './lib/utils/helmHelpers.js';
|
|
12
|
+
export { AWS_HELPERS, createHelper, FILE_ACCESS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, getHelpersByCategory, KUBERNETES_HELPERS, SPRIG_HELPERS, STANDARD_HELPERS, TEMPLATE_FUNCTION_HELPERS, type HelperDefinition, } from './lib/utils/helmHelpers.js';
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,8 @@ export * from './lib/helmChartWriter.js';
|
|
|
3
3
|
export * from './lib/rutter.js';
|
|
4
4
|
export * from './lib/security.js';
|
|
5
5
|
export * from './lib/umbrella.js';
|
|
6
|
+
export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
|
|
7
|
+
export { Subchart } from './lib/templates/subchart.js';
|
|
8
|
+
export { BasicChart } from './lib/templates/basic-chart.js';
|
|
6
9
|
export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
|
|
7
|
-
export { AWS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, STANDARD_HELPERS, } from './lib/utils/helmHelpers.js';
|
|
10
|
+
export { AWS_HELPERS, createHelper, FILE_ACCESS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, getHelpersByCategory, KUBERNETES_HELPERS, SPRIG_HELPERS, STANDARD_HELPERS, TEMPLATE_FUNCTION_HELPERS, } from './lib/utils/helmHelpers.js';
|
|
@@ -5,6 +5,7 @@ import { SecurityUtils } from './security.js';
|
|
|
5
5
|
export class HelmChartWriter {
|
|
6
6
|
static write(opts) {
|
|
7
7
|
const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
|
|
8
|
+
console.log(`📝 Writing Helm chart: ${meta.name} v${meta.version} to ${outDir}`);
|
|
8
9
|
const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
|
|
9
10
|
this.createDirectories(validatedOutDir);
|
|
10
11
|
this.writeChartYaml(validatedOutDir, meta);
|
|
@@ -14,6 +15,7 @@ export class HelmChartWriter {
|
|
|
14
15
|
this.writeNotes(validatedOutDir, notesTpl);
|
|
15
16
|
this.writeSchema(validatedOutDir, valuesSchema);
|
|
16
17
|
this.writeHelmIgnore(validatedOutDir);
|
|
18
|
+
console.log(`✅ Helm chart written successfully to ${validatedOutDir}`);
|
|
17
19
|
}
|
|
18
20
|
static createDirectories(outDir) {
|
|
19
21
|
fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
|
|
@@ -109,14 +111,21 @@ function writeAssets(outDir, assets) {
|
|
|
109
111
|
for (const asset of assets) {
|
|
110
112
|
const sanitizedId = asset.id.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
111
113
|
if (sanitizedId !== asset.id) {
|
|
114
|
+
console.error(`❌ Invalid asset ID detected: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
|
|
112
115
|
throw new Error(`Invalid asset ID: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
|
|
113
116
|
}
|
|
114
117
|
const targetDir = getTargetDirectory(asset.target);
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
try {
|
|
119
|
+
if (asset.singleFile) {
|
|
120
|
+
writeSingleAssetFile(outDir, targetDir, sanitizedId, asset.yaml);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
writeMultipleAssetFiles(outDir, targetDir, sanitizedId, asset.yaml);
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error(`❌ Failed to write asset ${asset.id}:`, error);
|
|
128
|
+
throw error;
|
|
120
129
|
}
|
|
121
130
|
}
|
|
122
131
|
}
|
|
@@ -124,14 +133,27 @@ function getTargetDirectory(target) {
|
|
|
124
133
|
return target === 'crds' ? 'crds' : 'templates';
|
|
125
134
|
}
|
|
126
135
|
function writeSingleAssetFile(outDir, targetDir, assetId, yaml) {
|
|
136
|
+
if (assetId === 'ingress' && yaml.includes('number:')) {
|
|
137
|
+
console.log('🔍 Writing ingress file with content:');
|
|
138
|
+
const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
|
|
139
|
+
console.log('Number lines:', numberLines);
|
|
140
|
+
}
|
|
127
141
|
const filename = `${assetId}.yaml`;
|
|
128
142
|
fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
|
|
129
143
|
fs.writeFileSync(path.join(outDir, targetDir, filename), yaml + '\n');
|
|
130
144
|
}
|
|
131
145
|
function writeMultipleAssetFiles(outDir, targetDir, assetId, yaml) {
|
|
146
|
+
if (assetId === 'ingress' && yaml.includes('number:')) {
|
|
147
|
+
console.log('🔍 Writing ingress file (multiple) with content:');
|
|
148
|
+
const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
|
|
149
|
+
console.log('Number lines:', numberLines);
|
|
150
|
+
}
|
|
132
151
|
const parts = splitDocs(yaml);
|
|
133
152
|
parts.forEach((doc, index) => {
|
|
134
153
|
const filename = `${assetId}${parts.length > 1 ? `-${index + 1}` : ''}.yaml`;
|
|
154
|
+
if (assetId === 'ingress' && doc.includes('number:')) {
|
|
155
|
+
console.log(`🔍 Writing document ${index + 1} for ingress:`, doc.split('\n').filter((line) => line.includes('number:')));
|
|
156
|
+
}
|
|
135
157
|
fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
|
|
136
158
|
fs.writeFileSync(path.join(outDir, targetDir, filename), doc + '\n');
|
|
137
159
|
});
|
package/dist/lib/rutter.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { ApiObject } from 'cdk8s';
|
|
2
2
|
import type { ChartProps } from 'cdk8s';
|
|
3
|
+
import type { Ingress, ServiceAccount } from 'cdk8s-plus-33';
|
|
3
4
|
import type { Construct } from 'constructs';
|
|
4
|
-
import type { ServiceAccount, Ingress } from 'cdk8s-plus-33';
|
|
5
5
|
import type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSECRServiceAccountSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec } from './resources/cloud/aws/awsResources.js';
|
|
6
6
|
import type { KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec } from './resources/cloud/aws/karpenterResources.js';
|
|
7
7
|
import type { HelperDefinition } from './utils/helmHelpers.js';
|
|
8
8
|
export declare class Rutter {
|
|
9
9
|
private static readonly HELPER_NAME;
|
|
10
10
|
private readonly app;
|
|
11
|
-
private readonly
|
|
11
|
+
private readonly assets;
|
|
12
12
|
private readonly awsResources;
|
|
13
|
-
private readonly
|
|
14
|
-
private readonly meta;
|
|
13
|
+
private readonly chart;
|
|
15
14
|
private readonly defaultValues;
|
|
16
15
|
private readonly envValues;
|
|
16
|
+
private readonly karpenterResources;
|
|
17
|
+
private readonly meta;
|
|
17
18
|
private readonly props;
|
|
18
|
-
private readonly assets;
|
|
19
19
|
constructor(props: RutterProps);
|
|
20
20
|
addAWSEBSStorageClass(spec: AWSEBSStorageClassSpec): ApiObject;
|
|
21
21
|
addAWSEFSStorageClass(spec: AWSEFSStorageClassSpec): ApiObject;
|
|
@@ -79,6 +79,7 @@ export declare class Rutter {
|
|
|
79
79
|
}): ApiObject;
|
|
80
80
|
addAWSECRServiceAccount(spec: AWSECRServiceAccountSpec): ServiceAccount;
|
|
81
81
|
addManifest(yamlOrObject: string | Record<string, unknown>, id: string): ApiObject;
|
|
82
|
+
addTemplateManifest(yamlTemplate: string, id: string): ApiObject;
|
|
82
83
|
addConditionalManifest(manifestObject: Record<string, unknown>, condition: string, id: string): ApiObject;
|
|
83
84
|
private validateManifestStructure;
|
|
84
85
|
getMeta(): ChartMetadata;
|
|
@@ -91,31 +92,42 @@ export declare class Rutter {
|
|
|
91
92
|
}>;
|
|
92
93
|
private toSynthArray;
|
|
93
94
|
private processHelmTemplates;
|
|
95
|
+
private fixCharacterMappingIssues;
|
|
96
|
+
private processCharacterMapping;
|
|
97
|
+
private applyHelmTemplateReplacements;
|
|
98
|
+
private fixIncludeStatements;
|
|
99
|
+
private fixFunctionCalls;
|
|
100
|
+
private fixChartReferences;
|
|
101
|
+
private removeHelmExpressionQuotes;
|
|
102
|
+
private fixConditionalBlocks;
|
|
103
|
+
private fixPipeExpressions;
|
|
104
|
+
private fixNestedQuotes;
|
|
105
|
+
private cleanupArtifacts;
|
|
94
106
|
write(outDir: string): void;
|
|
95
107
|
}
|
|
96
108
|
export interface ChartMetadata {
|
|
97
|
-
name: string;
|
|
98
|
-
version: string;
|
|
99
109
|
description?: string;
|
|
100
|
-
keywords?: string[];
|
|
101
110
|
home?: string;
|
|
102
|
-
|
|
111
|
+
keywords?: string[];
|
|
103
112
|
maintainers?: Array<{
|
|
104
|
-
name: string;
|
|
105
113
|
email?: string;
|
|
114
|
+
name: string;
|
|
106
115
|
url?: string;
|
|
107
116
|
}>;
|
|
117
|
+
name: string;
|
|
118
|
+
sources?: string[];
|
|
119
|
+
version: string;
|
|
108
120
|
}
|
|
109
121
|
export interface RutterProps {
|
|
110
|
-
|
|
111
|
-
|
|
122
|
+
chartProps?: ChartProps;
|
|
123
|
+
cloudProvider?: 'aws';
|
|
112
124
|
defaultValues?: Record<string, unknown>;
|
|
113
125
|
envValues?: Record<string, Record<string, unknown>>;
|
|
114
|
-
namespace?: string;
|
|
115
|
-
chartProps?: ChartProps;
|
|
116
126
|
helpersTpl?: string | HelperDefinition[];
|
|
117
|
-
cloudProvider?: 'aws';
|
|
118
127
|
manifestPrefix?: string;
|
|
128
|
+
meta: ChartMetadata;
|
|
129
|
+
namespace?: string;
|
|
130
|
+
scope?: Construct;
|
|
119
131
|
singleManifestFile?: boolean;
|
|
120
132
|
}
|
|
121
|
-
export type { AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec,
|
|
133
|
+
export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './resources/cloud/aws/awsResources.js';
|
package/dist/lib/rutter.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiObject, App, Chart, Testing } from 'cdk8s';
|
|
2
|
+
import Handlebars from 'handlebars';
|
|
2
3
|
import YAML from 'yaml';
|
|
3
|
-
import {
|
|
4
|
+
import { include } from './helm.js';
|
|
4
5
|
import { HelmChartWriter } from './helmChartWriter.js';
|
|
5
6
|
import { AWSResources } from './resources/cloud/aws/awsResources.js';
|
|
6
7
|
import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
|
|
@@ -8,10 +9,11 @@ import { generateHelpersTemplate } from './utils/helmHelpers.js';
|
|
|
8
9
|
export class Rutter {
|
|
9
10
|
constructor(props) {
|
|
10
11
|
this.assets = [];
|
|
11
|
-
|
|
12
|
-
this.meta = props.meta;
|
|
12
|
+
console.log(`🚀 Initializing Rutter chart: ${props.meta.name} v${props.meta.version}`);
|
|
13
13
|
this.defaultValues = props.defaultValues ?? {};
|
|
14
14
|
this.envValues = props.envValues ?? {};
|
|
15
|
+
this.meta = props.meta;
|
|
16
|
+
this.props = props;
|
|
15
17
|
this.app = new App();
|
|
16
18
|
this.chart = new Chart(this.app, props.meta.name, {
|
|
17
19
|
...props.chartProps,
|
|
@@ -74,6 +76,19 @@ export class Rutter {
|
|
|
74
76
|
spec: manifestObject['spec'],
|
|
75
77
|
});
|
|
76
78
|
}
|
|
79
|
+
addTemplateManifest(yamlTemplate, id) {
|
|
80
|
+
const templateAsset = {
|
|
81
|
+
id,
|
|
82
|
+
yaml: yamlTemplate,
|
|
83
|
+
target: 'templates',
|
|
84
|
+
};
|
|
85
|
+
this.assets.push(templateAsset);
|
|
86
|
+
return new ApiObject(this.chart, id, {
|
|
87
|
+
apiVersion: 'v1',
|
|
88
|
+
kind: 'ConfigMap',
|
|
89
|
+
metadata: { name: id },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
77
92
|
addConditionalManifest(manifestObject, condition, id) {
|
|
78
93
|
if (!condition || typeof condition !== 'string') {
|
|
79
94
|
throw new Error('Condition must be a non-empty string');
|
|
@@ -86,13 +101,32 @@ export class Rutter {
|
|
|
86
101
|
else {
|
|
87
102
|
helmCondition = `.Values.${condition}`;
|
|
88
103
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
104
|
+
try {
|
|
105
|
+
const yamlContent = YAML.stringify(manifestObject, {
|
|
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
|
+
});
|
|
119
|
+
const conditionalAsset = {
|
|
120
|
+
id,
|
|
121
|
+
yaml: conditionalYaml,
|
|
122
|
+
target: 'templates',
|
|
123
|
+
};
|
|
124
|
+
this.assets.push(conditionalAsset);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw new Error(`Failed to compile Handlebars template for manifest '${id}': ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
128
|
+
}
|
|
129
|
+
return new ApiObject(this.chart, `${id}-placeholder`, {
|
|
96
130
|
apiVersion: manifestObject['apiVersion'],
|
|
97
131
|
kind: manifestObject['kind'],
|
|
98
132
|
metadata: {
|
|
@@ -100,39 +134,33 @@ export class Rutter {
|
|
|
100
134
|
annotations: {
|
|
101
135
|
...(manifestObject['metadata']?.['annotations'] || {}),
|
|
102
136
|
'timonel.sh/conditional': condition,
|
|
137
|
+
'timonel.sh/placeholder': 'true',
|
|
103
138
|
},
|
|
104
139
|
},
|
|
105
140
|
spec: manifestObject['spec'],
|
|
106
141
|
});
|
|
107
142
|
}
|
|
108
143
|
validateManifestStructure(manifest) {
|
|
109
|
-
if (!manifest
|
|
110
|
-
|
|
144
|
+
if (!manifest.apiVersion) {
|
|
145
|
+
console.error('❌ Manifest validation failed: Missing apiVersion');
|
|
146
|
+
throw new Error('Manifest must have an apiVersion');
|
|
111
147
|
}
|
|
112
|
-
if (!manifest
|
|
113
|
-
|
|
148
|
+
if (!manifest.kind) {
|
|
149
|
+
console.error('❌ Manifest validation failed: Missing kind');
|
|
150
|
+
throw new Error('Manifest must have a kind');
|
|
114
151
|
}
|
|
115
|
-
if (!manifest
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
throw new Error('Kubernetes manifest must have valid metadata');
|
|
152
|
+
if (!manifest.metadata) {
|
|
153
|
+
console.error('❌ Manifest validation failed: Missing metadata');
|
|
154
|
+
throw new Error('Manifest must have metadata');
|
|
119
155
|
}
|
|
120
|
-
const metadata = manifest
|
|
121
|
-
if (!metadata
|
|
122
|
-
|
|
156
|
+
const metadata = manifest.metadata;
|
|
157
|
+
if (!metadata.name) {
|
|
158
|
+
console.error('❌ Manifest validation failed: Missing metadata.name');
|
|
159
|
+
throw new Error('Manifest metadata must have a name');
|
|
123
160
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
throw new Error('CustomResourceDefinition must have a valid spec');
|
|
128
|
-
}
|
|
129
|
-
const spec = manifest['spec'];
|
|
130
|
-
if (!spec['group'] || typeof spec['group'] !== 'string') {
|
|
131
|
-
throw new Error('CustomResourceDefinition spec must have a valid group');
|
|
132
|
-
}
|
|
133
|
-
if (!Array.isArray(spec['versions']) || spec['versions'].length === 0) {
|
|
134
|
-
throw new Error('CustomResourceDefinition must have at least one version in spec.versions');
|
|
135
|
-
}
|
|
161
|
+
if (typeof metadata.name !== 'string') {
|
|
162
|
+
console.error('❌ Manifest validation failed: metadata.name must be a string');
|
|
163
|
+
throw new Error('Manifest metadata.name must be a string');
|
|
136
164
|
}
|
|
137
165
|
}
|
|
138
166
|
getMeta() {
|
|
@@ -148,13 +176,23 @@ export class Rutter {
|
|
|
148
176
|
return [...this.assets];
|
|
149
177
|
}
|
|
150
178
|
toSynthArray() {
|
|
179
|
+
console.log(`📦 Synthesizing chart assets for: ${this.meta.name}`);
|
|
151
180
|
const apiObjectIds = [];
|
|
152
181
|
for (const child of this.chart.node.children) {
|
|
153
|
-
if (child instanceof ApiObject) {
|
|
182
|
+
if (child instanceof ApiObject && !child.node.id.endsWith('-placeholder')) {
|
|
154
183
|
apiObjectIds.push(child.node.id);
|
|
155
184
|
}
|
|
156
185
|
}
|
|
157
|
-
const
|
|
186
|
+
const allManifestObjs = Testing.synth(this.chart);
|
|
187
|
+
const manifestObjs = allManifestObjs.filter((obj) => {
|
|
188
|
+
if (obj && typeof obj === 'object') {
|
|
189
|
+
const o = obj;
|
|
190
|
+
const annotations = o.metadata?.annotations || {};
|
|
191
|
+
return annotations['timonel.sh/placeholder'] !== 'true';
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
console.log(`📋 Found ${manifestObjs.length} manifest objects to process`);
|
|
158
196
|
const enriched = manifestObjs.map((obj) => {
|
|
159
197
|
if (obj && typeof obj === 'object') {
|
|
160
198
|
const o = obj;
|
|
@@ -162,12 +200,12 @@ export class Rutter {
|
|
|
162
200
|
o.metadata.labels = o.metadata.labels ?? {};
|
|
163
201
|
const labels = o.metadata.labels;
|
|
164
202
|
const defaults = {
|
|
165
|
-
'helm.sh/chart':
|
|
166
|
-
'app.kubernetes.io/name': include
|
|
167
|
-
'app.kubernetes.io/instance':
|
|
168
|
-
'app.kubernetes.io/version':
|
|
203
|
+
'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
|
|
204
|
+
'app.kubernetes.io/name': `{{ ${include} "${Rutter.HELPER_NAME}" . }}`,
|
|
205
|
+
'app.kubernetes.io/instance': '{{ .Release.Name }}',
|
|
206
|
+
'app.kubernetes.io/version': '{{ .Chart.Version }}',
|
|
169
207
|
'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
|
|
170
|
-
'app.kubernetes.io/part-of':
|
|
208
|
+
'app.kubernetes.io/part-of': '{{ .Chart.Name }}',
|
|
171
209
|
};
|
|
172
210
|
for (const [key, value] of Object.entries(defaults)) {
|
|
173
211
|
if (!(key in labels)) {
|
|
@@ -188,10 +226,10 @@ export class Rutter {
|
|
|
188
226
|
}
|
|
189
227
|
else {
|
|
190
228
|
enriched.forEach((obj, index) => {
|
|
229
|
+
const apiObjectId = apiObjectIds[index];
|
|
230
|
+
const manifestId = apiObjectId || `manifest-${index + 1}`;
|
|
191
231
|
const yaml = this.processHelmTemplates(YAML.stringify(obj).trim());
|
|
192
232
|
if (yaml) {
|
|
193
|
-
const apiObjectId = apiObjectIds[index];
|
|
194
|
-
const manifestId = apiObjectId || `manifest-${index + 1}`;
|
|
195
233
|
synthAssets.push({ id: manifestId, yaml });
|
|
196
234
|
}
|
|
197
235
|
});
|
|
@@ -202,13 +240,174 @@ export class Rutter {
|
|
|
202
240
|
return synthAssets;
|
|
203
241
|
}
|
|
204
242
|
processHelmTemplates(yaml) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
243
|
+
let processed = this.fixCharacterMappingIssues(yaml);
|
|
244
|
+
processed = this.applyHelmTemplateReplacements(processed);
|
|
245
|
+
return processed;
|
|
246
|
+
}
|
|
247
|
+
fixCharacterMappingIssues(yaml) {
|
|
248
|
+
const lines = yaml.split('\n');
|
|
249
|
+
const fixedLines = [];
|
|
250
|
+
let i = 0;
|
|
251
|
+
while (i < lines.length) {
|
|
252
|
+
const line = lines[i];
|
|
253
|
+
if (!line) {
|
|
254
|
+
i++;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
const charKeyMatch = line.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
258
|
+
if (charKeyMatch && charKeyMatch[2] && parseInt(charKeyMatch[2]) === 0) {
|
|
259
|
+
const indent = charKeyMatch[1] || '';
|
|
260
|
+
const result = this.processCharacterMapping(lines, i, indent);
|
|
261
|
+
if (result.reconstructed) {
|
|
262
|
+
fixedLines.push(result.reconstructed);
|
|
263
|
+
i = result.nextIndex;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (line) {
|
|
268
|
+
fixedLines.push(line);
|
|
269
|
+
}
|
|
270
|
+
i++;
|
|
271
|
+
}
|
|
272
|
+
return fixedLines.join('\n');
|
|
273
|
+
}
|
|
274
|
+
processCharacterMapping(lines, startIndex, indent) {
|
|
275
|
+
const charMappings = [];
|
|
276
|
+
let j = startIndex;
|
|
277
|
+
while (j < lines.length) {
|
|
278
|
+
const currentLine = lines[j];
|
|
279
|
+
if (!currentLine) {
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
const currentMatch = currentLine.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
283
|
+
if (currentMatch && currentMatch[1] === indent && currentMatch[2] && currentMatch[3]) {
|
|
284
|
+
const index = parseInt(currentMatch[2]);
|
|
285
|
+
let char = currentMatch[3];
|
|
286
|
+
if (char && char.startsWith('"') && char.endsWith('"')) {
|
|
287
|
+
char = char.slice(1, -1);
|
|
288
|
+
}
|
|
289
|
+
charMappings.push({ index, char });
|
|
290
|
+
j++;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (charMappings.length > 3) {
|
|
297
|
+
charMappings.sort((a, b) => a.index - b.index);
|
|
298
|
+
const reconstructed = charMappings.map((m) => m.char).join('');
|
|
299
|
+
if (reconstructed.includes('{{') && reconstructed.includes('}}')) {
|
|
300
|
+
return { reconstructed: `${indent}${reconstructed}`, nextIndex: j };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { nextIndex: j };
|
|
304
|
+
}
|
|
305
|
+
applyHelmTemplateReplacements(processed) {
|
|
306
|
+
processed = this.fixConditionalBlocks(processed);
|
|
307
|
+
processed = this.fixPipeExpressions(processed);
|
|
308
|
+
processed = this.fixNestedQuotes(processed);
|
|
309
|
+
processed = this.cleanupArtifacts(processed);
|
|
310
|
+
processed = this.removeHelmExpressionQuotes(processed);
|
|
311
|
+
return processed;
|
|
312
|
+
}
|
|
313
|
+
fixIncludeStatements(processed) {
|
|
314
|
+
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \| nindent (\d+) \}\}/g, '{{ include "$1" . | nindent $2 }}');
|
|
315
|
+
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \}\}/g, '{{ include "$1" . }}');
|
|
316
|
+
return processed;
|
|
317
|
+
}
|
|
318
|
+
fixFunctionCalls(processed) {
|
|
319
|
+
return processed.replace(/\{\{ (\w+) \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ $1 "$2" $3 }}');
|
|
320
|
+
}
|
|
321
|
+
fixChartReferences(processed) {
|
|
322
|
+
return processed.replace(/\{\{ \.Chart\.(\w+) \| replace \\"([^"]*)\\" \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ .Chart.$1 | replace "$2" "$3" $4 }}');
|
|
323
|
+
}
|
|
324
|
+
removeHelmExpressionQuotes(processed) {
|
|
325
|
+
const HELM_EXPRESSION_REPLACEMENT = '$1$2: {{$3}}';
|
|
326
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\|\s*(?:int|float|bool|number)[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
327
|
+
processed = processed.replace(/^(\s*)(port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
328
|
+
processed = processed.replace(/^(\s*)(port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
329
|
+
processed = processed.replace(/^(\s*)(service\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
330
|
+
processed = processed.replace(/^(\s*)(backend\.service\.port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
331
|
+
processed = processed.replace(/^(\s*)(spec\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
332
|
+
processed = processed.replace(/^(\s*)([^:\s]*\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
333
|
+
processed = processed.replace(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
334
|
+
processed = processed.replace(/^(\s*)(replicas):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
335
|
+
processed = processed.replace(/^(\s*)(targetPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
336
|
+
processed = processed.replace(/^(\s*)(nodePort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
337
|
+
processed = processed.replace(/^(\s*)(containerPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
338
|
+
processed = processed.replace(/^(\s*)(hostPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
339
|
+
processed = processed.replace(/^(\s*)(weight):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
340
|
+
processed = processed.replace(/^(\s*)(priority):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
341
|
+
processed = processed.replace(/^(\s*)(timeoutSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
342
|
+
processed = processed.replace(/^(\s*)(periodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
343
|
+
processed = processed.replace(/^(\s*)(successThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
344
|
+
processed = processed.replace(/^(\s*)(failureThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
345
|
+
processed = processed.replace(/^(\s*)(initialDelaySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
346
|
+
processed = processed.replace(/^(\s*)(terminationGracePeriodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
347
|
+
processed = processed.replace(/^(\s*)(activeDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
348
|
+
processed = processed.replace(/^(\s*)(backoffLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
349
|
+
processed = processed.replace(/^(\s*)(parallelism):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
350
|
+
processed = processed.replace(/^(\s*)(completions):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
351
|
+
processed = processed.replace(/^(\s*)(revisionHistoryLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
352
|
+
processed = processed.replace(/^(\s*)(progressDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
353
|
+
processed = processed.replace(/^(\s*)(minReadySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
354
|
+
processed = processed.replace(/^(\s*)(maxUnavailable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
355
|
+
processed = processed.replace(/^(\s*)(maxSurge):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
356
|
+
processed = processed.replace(/^(\s*)(cpu):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
357
|
+
processed = processed.replace(/^(\s*)(memory):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
358
|
+
processed = processed.replace(/^(\s*)(enabled):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
359
|
+
processed = processed.replace(/^(\s*)(create):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
360
|
+
processed = processed.replace(/^(\s*)(allow):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
361
|
+
processed = processed.replace(/^(\s*)(disable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
362
|
+
processed = processed.replace(/^(\s*)(force):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
363
|
+
processed = processed.replace(/^(\s*)(required):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
364
|
+
processed = processed.replace(/^(\s*)(optional):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
365
|
+
processed = processed.replace(/^(\s*)(readOnly):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
366
|
+
processed = processed.replace(/^(\s*)(runAsNonRoot):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
367
|
+
processed = processed.replace(/^(\s*)(privileged):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
368
|
+
processed = processed.replace(/^(\s*)(allowPrivilegeEscalation):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
369
|
+
processed = processed.replace(/^(\s*)(readOnlyRootFilesystem):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
370
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*true[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
371
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*false[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
372
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\d+[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
373
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\beq\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
374
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bne\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
375
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\blt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
376
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\ble\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
377
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bgt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
378
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bge\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
379
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\band\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
380
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bor\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
381
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bnot\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
382
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[+][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
383
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[-][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
384
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[*][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
385
|
+
processed = processed.replace(/:\s*"([^"]*\{\{[^}]+\}\}[^"]*)"/g, ': $1');
|
|
386
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(true|false)"$/gm, '$1$2: $3');
|
|
387
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+)"$/gm, '$1$2: $3');
|
|
388
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.\d+)"$/gm, '$1$2: $3');
|
|
389
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.?\d*[eE][+-]?\d+)"$/gm, '$1$2: $3');
|
|
390
|
+
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
391
|
+
processed = processed.replace(/"\{\{([^}]*)\}\}"/g, '{{$1}}');
|
|
392
|
+
processed = processed.replace(/^(\s*)-\s*"\{\{([^}]*)\}\}"/gm, '$1- {{$2}}');
|
|
393
|
+
return processed;
|
|
394
|
+
}
|
|
395
|
+
fixConditionalBlocks(processed) {
|
|
396
|
+
return processed.replace(/"\{\{-\s*(if|with|range)([^}]*)\}\}([^"]*)\{\{-\s*end\s*\}\}"/g, '{{- $1$2}}$3{{- end }}');
|
|
397
|
+
}
|
|
398
|
+
fixPipeExpressions(processed) {
|
|
399
|
+
return processed.replace(/"\{\{([^}]*\|[^}]*)\}\}"/g, '{{$1}}');
|
|
400
|
+
}
|
|
401
|
+
fixNestedQuotes(processed) {
|
|
402
|
+
return processed.replace(/\{\{([^}]*)\\"([^"]*)\\"([^}]*)\}\}/g, '{{$1"$2"$3}}');
|
|
403
|
+
}
|
|
404
|
+
cleanupArtifacts(processed) {
|
|
405
|
+
processed = processed.replace(/": "\.nan"/g, ': .nan');
|
|
406
|
+
processed = processed.replace(/: \{\{([^}]+)\}\}$/gm, ': {{$1}}');
|
|
407
|
+
return processed;
|
|
210
408
|
}
|
|
211
409
|
write(outDir) {
|
|
410
|
+
console.log(`✍️ Writing Helm chart '${this.meta.name}' to: ${outDir}`);
|
|
212
411
|
let helpersContent;
|
|
213
412
|
if (this.props.helpersTpl) {
|
|
214
413
|
if (typeof this.props.helpersTpl === 'string') {
|
|
@@ -228,14 +427,17 @@ ${helper.template}
|
|
|
228
427
|
else {
|
|
229
428
|
helpersContent = generateHelpersTemplate(this.props.cloudProvider);
|
|
230
429
|
}
|
|
430
|
+
const synthAssets = this.toSynthArray();
|
|
431
|
+
console.log(`📄 Generated ${synthAssets.length} assets for chart`);
|
|
231
432
|
HelmChartWriter.write({
|
|
232
433
|
outDir,
|
|
233
434
|
meta: this.meta,
|
|
234
435
|
defaultValues: this.defaultValues,
|
|
235
436
|
envValues: this.envValues,
|
|
236
|
-
assets:
|
|
437
|
+
assets: synthAssets,
|
|
237
438
|
helpersTpl: helpersContent,
|
|
238
439
|
});
|
|
440
|
+
console.log(`✅ Helm chart '${this.meta.name}' written successfully`);
|
|
239
441
|
}
|
|
240
442
|
}
|
|
241
443
|
Rutter.HELPER_NAME = 'chart.name';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Chart } from 'cdk8s';
|
|
2
|
+
import type { ChartProps } from 'cdk8s';
|
|
3
|
+
import type { Construct } from 'constructs';
|
|
4
|
+
export interface BasicChartProps extends ChartProps {
|
|
5
|
+
appName?: string;
|
|
6
|
+
image?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
replicas?: number;
|
|
9
|
+
createNamespace?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class BasicChart extends Chart {
|
|
12
|
+
private rutter;
|
|
13
|
+
private props;
|
|
14
|
+
constructor(scope: Construct, id: string, props?: BasicChartProps);
|
|
15
|
+
private initializeRutter;
|
|
16
|
+
writeHelmChart(outDir: string): void;
|
|
17
|
+
private generateKubernetesManifests;
|
|
18
|
+
}
|
|
19
|
+
export declare function generateBasicChart(appName?: string): string;
|