kubetsx 0.1.2 → 0.1.3
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/dist/cli.js +1 -1
- package/package.json +6 -1
- package/examples/basic.tsx +0 -71
- package/examples/full-stack.tsx +0 -339
- package/examples/tsconfig.json +0 -21
- package/src/cli.ts +0 -111
- package/src/components/index.ts +0 -241
- package/src/index.ts +0 -123
- package/src/jsx-runtime.ts +0 -71
- package/src/render.ts +0 -863
- package/src/types.ts +0 -362
- package/tsconfig.examples.json +0 -18
- package/tsconfig.json +0 -22
package/src/render.ts
DELETED
|
@@ -1,863 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 🎯 Kubetsx - YAML Render Engine
|
|
3
|
-
*
|
|
4
|
-
* Transforms JSX element tree into Kubernetes YAML manifests
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { stringify } from 'yaml';
|
|
8
|
-
import * as fs from 'node:fs';
|
|
9
|
-
import * as path from 'node:path';
|
|
10
|
-
import type {
|
|
11
|
-
KubexElement,
|
|
12
|
-
KubexNode,
|
|
13
|
-
K8sResource,
|
|
14
|
-
RenderOptions,
|
|
15
|
-
ParsedContainer,
|
|
16
|
-
ParsedVolume
|
|
17
|
-
} from './types.js';
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Main Render Function
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Render a Kubex JSX tree to Kubernetes YAML
|
|
25
|
-
*/
|
|
26
|
-
export function render(element: KubexElement, options: RenderOptions = {}): string {
|
|
27
|
-
const resources = processElement(element, {});
|
|
28
|
-
const yamls = resources.map(r => stringify(r, { indent: 2 }));
|
|
29
|
-
const output = yamls.join('---\n');
|
|
30
|
-
|
|
31
|
-
if (options.output && !options.dryRun) {
|
|
32
|
-
if (options.splitFiles) {
|
|
33
|
-
// Write each resource to a separate file
|
|
34
|
-
resources.forEach((resource, i) => {
|
|
35
|
-
const filename = `${resource.metadata.name}-${resource.kind.toLowerCase()}.yaml`;
|
|
36
|
-
const filepath = path.join(options.output!, filename);
|
|
37
|
-
fs.mkdirSync(options.output!, { recursive: true });
|
|
38
|
-
fs.writeFileSync(filepath, stringify(resource, { indent: 2 }));
|
|
39
|
-
process.stderr.write(`✅ Generated: ${filepath}\n`);
|
|
40
|
-
});
|
|
41
|
-
} else {
|
|
42
|
-
// Write all resources to a single file
|
|
43
|
-
fs.mkdirSync(path.dirname(options.output), { recursive: true });
|
|
44
|
-
fs.writeFileSync(options.output, output);
|
|
45
|
-
process.stderr.write(`✅ Generated: ${options.output}\n`);
|
|
46
|
-
}
|
|
47
|
-
} else if (!options.dryRun) {
|
|
48
|
-
// Write YAML to stdout (clean output for piping)
|
|
49
|
-
process.stdout.write(output + '\n');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return output;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ============================================================================
|
|
56
|
-
// Element Processing
|
|
57
|
-
// ============================================================================
|
|
58
|
-
|
|
59
|
-
interface ProcessContext {
|
|
60
|
-
namespace?: string;
|
|
61
|
-
labels?: Record<string, string>;
|
|
62
|
-
currentDeployment?: Partial<K8sResource>;
|
|
63
|
-
containers?: ParsedContainer[];
|
|
64
|
-
volumes?: ParsedVolume[];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function processElement(node: KubexNode, ctx: ProcessContext): K8sResource[] {
|
|
68
|
-
if (!node || typeof node !== 'object' || !('type' in node)) {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const element = node as KubexElement;
|
|
73
|
-
|
|
74
|
-
// Handle function components
|
|
75
|
-
if (typeof element.type === 'function') {
|
|
76
|
-
const result = element.type({ ...element.props, children: element.children });
|
|
77
|
-
return result ? processElement(result, ctx) : [];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const type = element.type as string;
|
|
81
|
-
const props = element.props;
|
|
82
|
-
const children = element.children;
|
|
83
|
-
|
|
84
|
-
switch (type) {
|
|
85
|
-
case 'Fragment':
|
|
86
|
-
case 'Manifest':
|
|
87
|
-
return children.flatMap(child => processElement(child, ctx));
|
|
88
|
-
|
|
89
|
-
case 'Cluster':
|
|
90
|
-
return children.flatMap(child => processElement(child, ctx));
|
|
91
|
-
|
|
92
|
-
case 'Namespace':
|
|
93
|
-
return processNamespace(props, children, ctx);
|
|
94
|
-
|
|
95
|
-
case 'Deployment':
|
|
96
|
-
return processDeployment(props, children, ctx);
|
|
97
|
-
|
|
98
|
-
case 'Service':
|
|
99
|
-
return [processService(props, ctx)];
|
|
100
|
-
|
|
101
|
-
case 'Ingress':
|
|
102
|
-
return [processIngress(props, children, ctx)];
|
|
103
|
-
|
|
104
|
-
case 'ConfigMap':
|
|
105
|
-
return [processConfigMap(props, ctx)];
|
|
106
|
-
|
|
107
|
-
case 'Secret':
|
|
108
|
-
return [processSecret(props, ctx)];
|
|
109
|
-
|
|
110
|
-
case 'Pvc':
|
|
111
|
-
return [processPvc(props, ctx)];
|
|
112
|
-
|
|
113
|
-
case 'Hpa':
|
|
114
|
-
return [processHpa(props, ctx)];
|
|
115
|
-
|
|
116
|
-
case 'Job':
|
|
117
|
-
return processJob(props, children, ctx);
|
|
118
|
-
|
|
119
|
-
case 'CronJob':
|
|
120
|
-
return processCronJob(props, children, ctx);
|
|
121
|
-
|
|
122
|
-
case 'ServiceAccount':
|
|
123
|
-
return [processServiceAccount(props, ctx)];
|
|
124
|
-
|
|
125
|
-
case 'Role':
|
|
126
|
-
return [processRole(props, ctx)];
|
|
127
|
-
|
|
128
|
-
case 'ClusterRole':
|
|
129
|
-
return [processClusterRole(props, ctx)];
|
|
130
|
-
|
|
131
|
-
case 'RoleBinding':
|
|
132
|
-
return [processRoleBinding(props, ctx)];
|
|
133
|
-
|
|
134
|
-
case 'ClusterRoleBinding':
|
|
135
|
-
return [processClusterRoleBinding(props, ctx)];
|
|
136
|
-
|
|
137
|
-
default:
|
|
138
|
-
// Unknown element, process children
|
|
139
|
-
return children.flatMap(child => processElement(child, ctx));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ============================================================================
|
|
144
|
-
// Resource Processors
|
|
145
|
-
// ============================================================================
|
|
146
|
-
|
|
147
|
-
function processNamespace(
|
|
148
|
-
props: Record<string, unknown>,
|
|
149
|
-
children: KubexNode[],
|
|
150
|
-
ctx: ProcessContext
|
|
151
|
-
): K8sResource[] {
|
|
152
|
-
const name = props.name as string;
|
|
153
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
154
|
-
|
|
155
|
-
const nsResource: K8sResource = {
|
|
156
|
-
apiVersion: 'v1',
|
|
157
|
-
kind: 'Namespace',
|
|
158
|
-
metadata: { name, labels },
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const childResources = children.flatMap(child =>
|
|
162
|
-
processElement(child, { ...ctx, namespace: name })
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
return [nsResource, ...childResources];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function processDeployment(
|
|
169
|
-
props: Record<string, unknown>,
|
|
170
|
-
children: KubexNode[],
|
|
171
|
-
ctx: ProcessContext
|
|
172
|
-
): K8sResource[] {
|
|
173
|
-
const name = props.name as string;
|
|
174
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
175
|
-
const replicas = (props.replicas as number) ?? 1;
|
|
176
|
-
const labels = { app: name, ...(props.labels as Record<string, string>) };
|
|
177
|
-
const annotations = props.annotations as Record<string, string> | undefined;
|
|
178
|
-
const selector = (props.selector as Record<string, string>) || { app: name };
|
|
179
|
-
const strategy = props.strategy as string | undefined;
|
|
180
|
-
|
|
181
|
-
// Process children to extract containers, volumes, etc.
|
|
182
|
-
const containers: ParsedContainer[] = [];
|
|
183
|
-
const volumes: ParsedVolume[] = [];
|
|
184
|
-
|
|
185
|
-
for (const child of children) {
|
|
186
|
-
processDeploymentChild(child, containers, volumes, ctx);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const resource: K8sResource = {
|
|
190
|
-
apiVersion: 'apps/v1',
|
|
191
|
-
kind: 'Deployment',
|
|
192
|
-
metadata: {
|
|
193
|
-
name,
|
|
194
|
-
namespace,
|
|
195
|
-
labels,
|
|
196
|
-
annotations,
|
|
197
|
-
},
|
|
198
|
-
spec: {
|
|
199
|
-
replicas,
|
|
200
|
-
selector: { matchLabels: selector },
|
|
201
|
-
...(strategy && { strategy: { type: strategy } }),
|
|
202
|
-
template: {
|
|
203
|
-
metadata: { labels },
|
|
204
|
-
spec: {
|
|
205
|
-
containers,
|
|
206
|
-
...(volumes.length > 0 && { volumes }),
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
return [resource];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function processDeploymentChild(
|
|
216
|
-
node: KubexNode,
|
|
217
|
-
containers: ParsedContainer[],
|
|
218
|
-
volumes: ParsedVolume[],
|
|
219
|
-
ctx: ProcessContext
|
|
220
|
-
): void {
|
|
221
|
-
if (!node || typeof node !== 'object' || !('type' in node)) return;
|
|
222
|
-
|
|
223
|
-
const element = node as KubexElement;
|
|
224
|
-
|
|
225
|
-
// Handle function components
|
|
226
|
-
if (typeof element.type === 'function') {
|
|
227
|
-
const result = element.type({ ...element.props, children: element.children });
|
|
228
|
-
if (result) processDeploymentChild(result, containers, volumes, ctx);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const type = element.type as string;
|
|
233
|
-
|
|
234
|
-
if (type === 'Fragment') {
|
|
235
|
-
for (const child of element.children) {
|
|
236
|
-
processDeploymentChild(child, containers, volumes, ctx);
|
|
237
|
-
}
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (type === 'Container') {
|
|
242
|
-
containers.push(processContainer(element));
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (type === 'Volume') {
|
|
247
|
-
volumes.push(processVolume(element));
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function processContainer(element: KubexElement): ParsedContainer {
|
|
253
|
-
const props = element.props;
|
|
254
|
-
const children = element.children;
|
|
255
|
-
|
|
256
|
-
const container: ParsedContainer = {
|
|
257
|
-
name: props.name as string,
|
|
258
|
-
image: props.image as string,
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
if (props.imagePullPolicy) container.imagePullPolicy = props.imagePullPolicy as string;
|
|
262
|
-
if (props.command) container.command = props.command as string[];
|
|
263
|
-
if (props.args) container.args = props.args as string[];
|
|
264
|
-
if (props.workingDir) container.workingDir = props.workingDir as string;
|
|
265
|
-
|
|
266
|
-
// Process children
|
|
267
|
-
for (const child of children) {
|
|
268
|
-
processContainerChild(child, container);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return container;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function processContainerChild(node: KubexNode, container: ParsedContainer): void {
|
|
275
|
-
if (!node || typeof node !== 'object' || !('type' in node)) return;
|
|
276
|
-
|
|
277
|
-
const element = node as KubexElement;
|
|
278
|
-
|
|
279
|
-
if (typeof element.type === 'function') {
|
|
280
|
-
const result = element.type({ ...element.props, children: element.children });
|
|
281
|
-
if (result) processContainerChild(result, container);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const type = element.type as string;
|
|
286
|
-
const props = element.props;
|
|
287
|
-
|
|
288
|
-
switch (type) {
|
|
289
|
-
case 'Fragment':
|
|
290
|
-
for (const child of element.children) {
|
|
291
|
-
processContainerChild(child, container);
|
|
292
|
-
}
|
|
293
|
-
break;
|
|
294
|
-
|
|
295
|
-
case 'Port':
|
|
296
|
-
container.ports = container.ports || [];
|
|
297
|
-
const portEntry: { containerPort: number; name?: string; protocol?: string } = {
|
|
298
|
-
containerPort: props.container as number,
|
|
299
|
-
};
|
|
300
|
-
if (props.name) portEntry.name = props.name as string;
|
|
301
|
-
if (props.protocol) portEntry.protocol = props.protocol as string;
|
|
302
|
-
container.ports.push(portEntry);
|
|
303
|
-
break;
|
|
304
|
-
|
|
305
|
-
case 'Env':
|
|
306
|
-
container.env = container.env || [];
|
|
307
|
-
const envEntry: { name: string; value?: string; valueFrom?: unknown } = {
|
|
308
|
-
name: props.name as string,
|
|
309
|
-
};
|
|
310
|
-
if (props.value !== undefined) {
|
|
311
|
-
envEntry.value = props.value as string;
|
|
312
|
-
} else if (element.children.length > 0) {
|
|
313
|
-
const valueFrom = processEnvChild(element.children[0]);
|
|
314
|
-
if (valueFrom) envEntry.valueFrom = valueFrom;
|
|
315
|
-
}
|
|
316
|
-
container.env.push(envEntry);
|
|
317
|
-
break;
|
|
318
|
-
|
|
319
|
-
case 'Resources':
|
|
320
|
-
const requests: Record<string, string> = {};
|
|
321
|
-
const limits: Record<string, string> = {};
|
|
322
|
-
if (props.requestMemory) requests.memory = props.requestMemory as string;
|
|
323
|
-
if (props.requestCpu) requests.cpu = props.requestCpu as string;
|
|
324
|
-
if (props.requestEphemeralStorage) requests['ephemeral-storage'] = props.requestEphemeralStorage as string;
|
|
325
|
-
if (props.limitMemory) limits.memory = props.limitMemory as string;
|
|
326
|
-
if (props.limitCpu) limits.cpu = props.limitCpu as string;
|
|
327
|
-
if (props.limitEphemeralStorage) limits['ephemeral-storage'] = props.limitEphemeralStorage as string;
|
|
328
|
-
container.resources = { requests, limits };
|
|
329
|
-
break;
|
|
330
|
-
|
|
331
|
-
case 'Probe':
|
|
332
|
-
const probeType = props.type as string;
|
|
333
|
-
const probe = processProbe(element);
|
|
334
|
-
if (probeType === 'liveness') container.livenessProbe = probe;
|
|
335
|
-
else if (probeType === 'readiness') container.readinessProbe = probe;
|
|
336
|
-
else if (probeType === 'startup') container.startupProbe = probe;
|
|
337
|
-
break;
|
|
338
|
-
|
|
339
|
-
case 'VolumeMount':
|
|
340
|
-
container.volumeMounts = container.volumeMounts || [];
|
|
341
|
-
const volumeMount: { name: string; mountPath: string; subPath?: string; readOnly?: boolean } = {
|
|
342
|
-
name: props.name as string,
|
|
343
|
-
mountPath: props.mountPath as string,
|
|
344
|
-
};
|
|
345
|
-
if (props.subPath) volumeMount.subPath = props.subPath as string;
|
|
346
|
-
if (props.readOnly) volumeMount.readOnly = props.readOnly as boolean;
|
|
347
|
-
container.volumeMounts.push(volumeMount);
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function resolveElement(node: KubexNode): KubexElement | null {
|
|
353
|
-
if (!node || typeof node !== 'object' || !('type' in node)) return null;
|
|
354
|
-
|
|
355
|
-
let element = node as KubexElement;
|
|
356
|
-
|
|
357
|
-
// If type is a function component, call it to get the actual element
|
|
358
|
-
while (typeof element.type === 'function') {
|
|
359
|
-
const result = element.type({ ...element.props, children: element.children });
|
|
360
|
-
if (!result || typeof result !== 'object' || !('type' in result)) return null;
|
|
361
|
-
element = result;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
return element;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function processEnvChild(node: KubexNode): unknown {
|
|
368
|
-
const element = resolveElement(node);
|
|
369
|
-
if (!element) return null;
|
|
370
|
-
|
|
371
|
-
const type = element.type as string;
|
|
372
|
-
const props = element.props;
|
|
373
|
-
|
|
374
|
-
if (type === 'SecretRef') {
|
|
375
|
-
return {
|
|
376
|
-
secretKeyRef: {
|
|
377
|
-
name: props.name as string,
|
|
378
|
-
key: props.secretKey as string,
|
|
379
|
-
},
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (type === 'ConfigMapRef') {
|
|
384
|
-
return {
|
|
385
|
-
configMapKeyRef: {
|
|
386
|
-
name: props.name as string,
|
|
387
|
-
key: props.configKey as string,
|
|
388
|
-
},
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function processProbe(element: KubexElement): unknown {
|
|
396
|
-
const props = element.props;
|
|
397
|
-
const children = element.children;
|
|
398
|
-
|
|
399
|
-
const probe: Record<string, unknown> = {};
|
|
400
|
-
|
|
401
|
-
if (props.delay) probe.initialDelaySeconds = props.delay as number;
|
|
402
|
-
if (props.period) probe.periodSeconds = props.period as number;
|
|
403
|
-
if (props.timeout) probe.timeoutSeconds = props.timeout as number;
|
|
404
|
-
if (props.successThreshold) probe.successThreshold = props.successThreshold as number;
|
|
405
|
-
if (props.failureThreshold) probe.failureThreshold = props.failureThreshold as number;
|
|
406
|
-
|
|
407
|
-
for (const child of children) {
|
|
408
|
-
const childEl = resolveElement(child);
|
|
409
|
-
if (!childEl) continue;
|
|
410
|
-
|
|
411
|
-
const type = childEl.type as string;
|
|
412
|
-
const childProps = childEl.props;
|
|
413
|
-
|
|
414
|
-
if (type === 'HttpProbe') {
|
|
415
|
-
const httpGet: { path: string; port: number; scheme?: string } = {
|
|
416
|
-
path: childProps.path as string,
|
|
417
|
-
port: childProps.port as number,
|
|
418
|
-
};
|
|
419
|
-
if (childProps.scheme) httpGet.scheme = childProps.scheme as string;
|
|
420
|
-
probe.httpGet = httpGet;
|
|
421
|
-
} else if (type === 'TcpProbe') {
|
|
422
|
-
probe.tcpSocket = { port: childProps.port as number };
|
|
423
|
-
} else if (type === 'ExecProbe') {
|
|
424
|
-
probe.exec = { command: childProps.command as string[] };
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
return probe;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function processVolume(element: KubexElement): ParsedVolume {
|
|
432
|
-
const name = element.props.name as string;
|
|
433
|
-
const volume: ParsedVolume = { name };
|
|
434
|
-
|
|
435
|
-
for (const child of element.children) {
|
|
436
|
-
const childEl = resolveElement(child);
|
|
437
|
-
if (!childEl) continue;
|
|
438
|
-
|
|
439
|
-
const type = childEl.type as string;
|
|
440
|
-
const props = childEl.props;
|
|
441
|
-
|
|
442
|
-
if (type === 'EmptyDir') {
|
|
443
|
-
const emptyDir: { medium?: string; sizeLimit?: string } = {};
|
|
444
|
-
if (props.medium) emptyDir.medium = props.medium as string;
|
|
445
|
-
if (props.sizeLimit) emptyDir.sizeLimit = props.sizeLimit as string;
|
|
446
|
-
volume.emptyDir = emptyDir;
|
|
447
|
-
} else if (type === 'PvcVolume') {
|
|
448
|
-
const pvc: { claimName: string; readOnly?: boolean } = {
|
|
449
|
-
claimName: props.claimName as string,
|
|
450
|
-
};
|
|
451
|
-
if (props.readOnly) pvc.readOnly = props.readOnly as boolean;
|
|
452
|
-
volume.persistentVolumeClaim = pvc;
|
|
453
|
-
} else if (type === 'ConfigMapVolume') {
|
|
454
|
-
const configMap: { name: string; items?: { key: string; path: string }[] } = {
|
|
455
|
-
name: props.name as string,
|
|
456
|
-
};
|
|
457
|
-
if (props.items) configMap.items = props.items as { key: string; path: string }[];
|
|
458
|
-
volume.configMap = configMap;
|
|
459
|
-
} else if (type === 'SecretVolume') {
|
|
460
|
-
const secret: { secretName: string; items?: { key: string; path: string }[] } = {
|
|
461
|
-
secretName: props.secretName as string,
|
|
462
|
-
};
|
|
463
|
-
if (props.items) secret.items = props.items as { key: string; path: string }[];
|
|
464
|
-
volume.secret = secret;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return volume;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function processService(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
472
|
-
const name = props.name as string;
|
|
473
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
474
|
-
const type = (props.type as string) || 'ClusterIP';
|
|
475
|
-
const port = props.port as number;
|
|
476
|
-
const targetPort = (props.targetPort as number) || port;
|
|
477
|
-
const nodePort = props.nodePort as number | undefined;
|
|
478
|
-
const selector = (props.selector as Record<string, string>) || { app: name };
|
|
479
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
480
|
-
|
|
481
|
-
return {
|
|
482
|
-
apiVersion: 'v1',
|
|
483
|
-
kind: 'Service',
|
|
484
|
-
metadata: { name, namespace, labels },
|
|
485
|
-
spec: {
|
|
486
|
-
type,
|
|
487
|
-
selector,
|
|
488
|
-
ports: [
|
|
489
|
-
{
|
|
490
|
-
port,
|
|
491
|
-
targetPort,
|
|
492
|
-
...(nodePort && { nodePort }),
|
|
493
|
-
},
|
|
494
|
-
],
|
|
495
|
-
},
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function processIngress(
|
|
500
|
-
props: Record<string, unknown>,
|
|
501
|
-
children: KubexNode[],
|
|
502
|
-
ctx: ProcessContext
|
|
503
|
-
): K8sResource {
|
|
504
|
-
const name = props.name as string;
|
|
505
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
506
|
-
const className = props.className as string | undefined;
|
|
507
|
-
const ssl = props.ssl as boolean | undefined;
|
|
508
|
-
const annotations = {
|
|
509
|
-
...(ssl && { 'nginx.ingress.kubernetes.io/ssl-redirect': 'true' }),
|
|
510
|
-
...(props.annotations as Record<string, string>),
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const rules: unknown[] = [];
|
|
514
|
-
const tls: unknown[] = [];
|
|
515
|
-
|
|
516
|
-
for (const child of children) {
|
|
517
|
-
const childEl = resolveElement(child);
|
|
518
|
-
if (!childEl) continue;
|
|
519
|
-
|
|
520
|
-
const childType = childEl.type as string;
|
|
521
|
-
|
|
522
|
-
if (childType === 'IngressHost') {
|
|
523
|
-
const host = childEl.props.host as string;
|
|
524
|
-
const paths: unknown[] = [];
|
|
525
|
-
|
|
526
|
-
for (const routeChild of childEl.children) {
|
|
527
|
-
const routeEl = resolveElement(routeChild);
|
|
528
|
-
if (!routeEl) continue;
|
|
529
|
-
|
|
530
|
-
if (routeEl.type === 'Route') {
|
|
531
|
-
paths.push({
|
|
532
|
-
path: routeEl.props.path as string,
|
|
533
|
-
pathType: (routeEl.props.pathType as string) || 'Prefix',
|
|
534
|
-
backend: {
|
|
535
|
-
service: {
|
|
536
|
-
name: routeEl.props.service as string,
|
|
537
|
-
port: { number: routeEl.props.port as number },
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
rules.push({ host, http: { paths } });
|
|
545
|
-
if (ssl) {
|
|
546
|
-
tls.push({ hosts: [host], secretName: `${name}-tls` });
|
|
547
|
-
}
|
|
548
|
-
} else if (childType === 'Route') {
|
|
549
|
-
// Direct route without host wrapper
|
|
550
|
-
const hostFromProps = props.host as string | undefined;
|
|
551
|
-
if (hostFromProps) {
|
|
552
|
-
const existingRule = rules.find((r: any) => r.host === hostFromProps);
|
|
553
|
-
if (existingRule) {
|
|
554
|
-
(existingRule as any).http.paths.push({
|
|
555
|
-
path: childEl.props.path as string,
|
|
556
|
-
pathType: (childEl.props.pathType as string) || 'Prefix',
|
|
557
|
-
backend: {
|
|
558
|
-
service: {
|
|
559
|
-
name: childEl.props.service as string,
|
|
560
|
-
port: { number: childEl.props.port as number },
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
});
|
|
564
|
-
} else {
|
|
565
|
-
rules.push({
|
|
566
|
-
host: hostFromProps,
|
|
567
|
-
http: {
|
|
568
|
-
paths: [{
|
|
569
|
-
path: childEl.props.path as string,
|
|
570
|
-
pathType: (childEl.props.pathType as string) || 'Prefix',
|
|
571
|
-
backend: {
|
|
572
|
-
service: {
|
|
573
|
-
name: childEl.props.service as string,
|
|
574
|
-
port: { number: childEl.props.port as number },
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
}],
|
|
578
|
-
},
|
|
579
|
-
});
|
|
580
|
-
if (ssl) {
|
|
581
|
-
tls.push({ hosts: [hostFromProps], secretName: `${name}-tls` });
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return {
|
|
589
|
-
apiVersion: 'networking.k8s.io/v1',
|
|
590
|
-
kind: 'Ingress',
|
|
591
|
-
metadata: { name, namespace, annotations },
|
|
592
|
-
spec: {
|
|
593
|
-
...(className && { ingressClassName: className }),
|
|
594
|
-
rules,
|
|
595
|
-
...(tls.length > 0 && { tls }),
|
|
596
|
-
},
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function processConfigMap(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
601
|
-
const name = props.name as string;
|
|
602
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
603
|
-
const data = props.data as Record<string, string> | undefined;
|
|
604
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
605
|
-
|
|
606
|
-
return {
|
|
607
|
-
apiVersion: 'v1',
|
|
608
|
-
kind: 'ConfigMap',
|
|
609
|
-
metadata: { name, namespace, labels },
|
|
610
|
-
data,
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function processSecret(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
615
|
-
const name = props.name as string;
|
|
616
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
617
|
-
const type = (props.type as string) || 'Opaque';
|
|
618
|
-
const data = props.data as Record<string, string> | undefined;
|
|
619
|
-
const stringData = props.stringData as Record<string, string> | undefined;
|
|
620
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
621
|
-
|
|
622
|
-
return {
|
|
623
|
-
apiVersion: 'v1',
|
|
624
|
-
kind: 'Secret',
|
|
625
|
-
metadata: { name, namespace, labels },
|
|
626
|
-
...(type && { type }),
|
|
627
|
-
...(data && { data }),
|
|
628
|
-
...(stringData && { stringData }),
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function processPvc(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
633
|
-
const name = props.name as string;
|
|
634
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
635
|
-
const storage = props.storage as string;
|
|
636
|
-
const accessModes = (props.accessModes as string[]) || ['ReadWriteOnce'];
|
|
637
|
-
const storageClassName = props.storageClassName as string | undefined;
|
|
638
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
639
|
-
|
|
640
|
-
return {
|
|
641
|
-
apiVersion: 'v1',
|
|
642
|
-
kind: 'PersistentVolumeClaim',
|
|
643
|
-
metadata: { name, namespace, labels },
|
|
644
|
-
spec: {
|
|
645
|
-
accessModes,
|
|
646
|
-
resources: {
|
|
647
|
-
requests: { storage },
|
|
648
|
-
},
|
|
649
|
-
...(storageClassName && { storageClassName }),
|
|
650
|
-
},
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function processHpa(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
655
|
-
const name = props.name as string;
|
|
656
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
657
|
-
const target = props.target as string;
|
|
658
|
-
const minReplicas = (props.minReplicas as number) || 1;
|
|
659
|
-
const maxReplicas = props.maxReplicas as number;
|
|
660
|
-
const targetCpuUtilization = props.targetCpuUtilization as number | undefined;
|
|
661
|
-
const targetMemoryUtilization = props.targetMemoryUtilization as number | undefined;
|
|
662
|
-
|
|
663
|
-
const metrics: unknown[] = [];
|
|
664
|
-
if (targetCpuUtilization) {
|
|
665
|
-
metrics.push({
|
|
666
|
-
type: 'Resource',
|
|
667
|
-
resource: {
|
|
668
|
-
name: 'cpu',
|
|
669
|
-
target: { type: 'Utilization', averageUtilization: targetCpuUtilization },
|
|
670
|
-
},
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
if (targetMemoryUtilization) {
|
|
674
|
-
metrics.push({
|
|
675
|
-
type: 'Resource',
|
|
676
|
-
resource: {
|
|
677
|
-
name: 'memory',
|
|
678
|
-
target: { type: 'Utilization', averageUtilization: targetMemoryUtilization },
|
|
679
|
-
},
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
return {
|
|
684
|
-
apiVersion: 'autoscaling/v2',
|
|
685
|
-
kind: 'HorizontalPodAutoscaler',
|
|
686
|
-
metadata: { name, namespace },
|
|
687
|
-
spec: {
|
|
688
|
-
scaleTargetRef: {
|
|
689
|
-
apiVersion: 'apps/v1',
|
|
690
|
-
kind: 'Deployment',
|
|
691
|
-
name: target,
|
|
692
|
-
},
|
|
693
|
-
minReplicas,
|
|
694
|
-
maxReplicas,
|
|
695
|
-
...(metrics.length > 0 && { metrics }),
|
|
696
|
-
},
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
function processJob(
|
|
701
|
-
props: Record<string, unknown>,
|
|
702
|
-
children: KubexNode[],
|
|
703
|
-
ctx: ProcessContext
|
|
704
|
-
): K8sResource[] {
|
|
705
|
-
const name = props.name as string;
|
|
706
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
707
|
-
const backoffLimit = props.backoffLimit as number | undefined;
|
|
708
|
-
const ttlSecondsAfterFinished = props.ttlSecondsAfterFinished as number | undefined;
|
|
709
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
710
|
-
|
|
711
|
-
const containers: ParsedContainer[] = [];
|
|
712
|
-
const volumes: ParsedVolume[] = [];
|
|
713
|
-
|
|
714
|
-
for (const child of children) {
|
|
715
|
-
processDeploymentChild(child, containers, volumes, ctx);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return [{
|
|
719
|
-
apiVersion: 'batch/v1',
|
|
720
|
-
kind: 'Job',
|
|
721
|
-
metadata: { name, namespace, labels },
|
|
722
|
-
spec: {
|
|
723
|
-
...(backoffLimit !== undefined && { backoffLimit }),
|
|
724
|
-
...(ttlSecondsAfterFinished !== undefined && { ttlSecondsAfterFinished }),
|
|
725
|
-
template: {
|
|
726
|
-
spec: {
|
|
727
|
-
containers,
|
|
728
|
-
restartPolicy: 'Never',
|
|
729
|
-
...(volumes.length > 0 && { volumes }),
|
|
730
|
-
},
|
|
731
|
-
},
|
|
732
|
-
},
|
|
733
|
-
}];
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function processCronJob(
|
|
737
|
-
props: Record<string, unknown>,
|
|
738
|
-
children: KubexNode[],
|
|
739
|
-
ctx: ProcessContext
|
|
740
|
-
): K8sResource[] {
|
|
741
|
-
const name = props.name as string;
|
|
742
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
743
|
-
const schedule = props.schedule as string;
|
|
744
|
-
const concurrencyPolicy = props.concurrencyPolicy as string | undefined;
|
|
745
|
-
const suspend = props.suspend as boolean | undefined;
|
|
746
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
747
|
-
|
|
748
|
-
const containers: ParsedContainer[] = [];
|
|
749
|
-
const volumes: ParsedVolume[] = [];
|
|
750
|
-
|
|
751
|
-
for (const child of children) {
|
|
752
|
-
processDeploymentChild(child, containers, volumes, ctx);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return [{
|
|
756
|
-
apiVersion: 'batch/v1',
|
|
757
|
-
kind: 'CronJob',
|
|
758
|
-
metadata: { name, namespace, labels },
|
|
759
|
-
spec: {
|
|
760
|
-
schedule,
|
|
761
|
-
...(concurrencyPolicy && { concurrencyPolicy }),
|
|
762
|
-
...(suspend !== undefined && { suspend }),
|
|
763
|
-
jobTemplate: {
|
|
764
|
-
spec: {
|
|
765
|
-
template: {
|
|
766
|
-
spec: {
|
|
767
|
-
containers,
|
|
768
|
-
restartPolicy: 'Never',
|
|
769
|
-
...(volumes.length > 0 && { volumes }),
|
|
770
|
-
},
|
|
771
|
-
},
|
|
772
|
-
},
|
|
773
|
-
},
|
|
774
|
-
},
|
|
775
|
-
}];
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function processServiceAccount(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
779
|
-
const name = props.name as string;
|
|
780
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
781
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
782
|
-
|
|
783
|
-
return {
|
|
784
|
-
apiVersion: 'v1',
|
|
785
|
-
kind: 'ServiceAccount',
|
|
786
|
-
metadata: { name, namespace, labels },
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
function processRole(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
791
|
-
const name = props.name as string;
|
|
792
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
793
|
-
const rules = props.rules as { apiGroups: string[]; resources: string[]; verbs: string[] }[];
|
|
794
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
795
|
-
|
|
796
|
-
return {
|
|
797
|
-
apiVersion: 'rbac.authorization.k8s.io/v1',
|
|
798
|
-
kind: 'Role',
|
|
799
|
-
metadata: { name, namespace, labels },
|
|
800
|
-
rules,
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
function processClusterRole(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
805
|
-
const name = props.name as string;
|
|
806
|
-
const rules = props.rules as { apiGroups: string[]; resources: string[]; verbs: string[] }[];
|
|
807
|
-
const labels = props.labels as Record<string, string> | undefined;
|
|
808
|
-
|
|
809
|
-
return {
|
|
810
|
-
apiVersion: 'rbac.authorization.k8s.io/v1',
|
|
811
|
-
kind: 'ClusterRole',
|
|
812
|
-
metadata: { name, labels },
|
|
813
|
-
rules,
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
function processRoleBinding(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
818
|
-
const name = props.name as string;
|
|
819
|
-
const namespace = (props.namespace as string) || ctx.namespace;
|
|
820
|
-
const roleRef = props.roleRef as { kind: string; name: string };
|
|
821
|
-
const subjects = props.subjects as { kind: string; name: string; namespace?: string }[];
|
|
822
|
-
|
|
823
|
-
return {
|
|
824
|
-
apiVersion: 'rbac.authorization.k8s.io/v1',
|
|
825
|
-
kind: 'RoleBinding',
|
|
826
|
-
metadata: { name, namespace },
|
|
827
|
-
roleRef: {
|
|
828
|
-
apiGroup: 'rbac.authorization.k8s.io',
|
|
829
|
-
kind: roleRef.kind,
|
|
830
|
-
name: roleRef.name,
|
|
831
|
-
},
|
|
832
|
-
subjects: subjects.map(s => ({
|
|
833
|
-
kind: s.kind,
|
|
834
|
-
name: s.name,
|
|
835
|
-
...(s.namespace && { namespace: s.namespace }),
|
|
836
|
-
})),
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
function processClusterRoleBinding(props: Record<string, unknown>, ctx: ProcessContext): K8sResource {
|
|
841
|
-
const name = props.name as string;
|
|
842
|
-
const roleRef = props.roleRef as { kind: string; name: string };
|
|
843
|
-
const subjects = props.subjects as { kind: string; name: string; namespace?: string }[];
|
|
844
|
-
|
|
845
|
-
return {
|
|
846
|
-
apiVersion: 'rbac.authorization.k8s.io/v1',
|
|
847
|
-
kind: 'ClusterRoleBinding',
|
|
848
|
-
metadata: { name },
|
|
849
|
-
roleRef: {
|
|
850
|
-
apiGroup: 'rbac.authorization.k8s.io',
|
|
851
|
-
kind: roleRef.kind,
|
|
852
|
-
name: roleRef.name,
|
|
853
|
-
},
|
|
854
|
-
subjects: subjects.map(s => ({
|
|
855
|
-
kind: s.kind,
|
|
856
|
-
name: s.name,
|
|
857
|
-
...(s.namespace && { namespace: s.namespace }),
|
|
858
|
-
})),
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
export default render;
|
|
863
|
-
|