@kustodian/cli 1.1.0 → 1.2.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/package.json +10 -10
- package/src/bin.ts +0 -0
- package/src/commands/apply.ts +33 -0
- package/src/commands/update.ts +72 -12
- package/src/commands/validate.ts +41 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kustodian/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI framework with DI and middleware for Kustodian",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
"registry": "https://npm.pkg.github.com"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@kustodian/core": "
|
|
41
|
-
"@kustodian/generator": "
|
|
42
|
-
"@kustodian/loader": "
|
|
43
|
-
"@kustodian/nodes": "
|
|
44
|
-
"@kustodian/plugin-k0s": "
|
|
45
|
-
"@kustodian/plugins": "
|
|
46
|
-
"@kustodian/registry": "
|
|
47
|
-
"@kustodian/schema": "
|
|
48
|
-
"@kustodian/sources": "
|
|
40
|
+
"@kustodian/core": "1.1.0",
|
|
41
|
+
"@kustodian/generator": "1.2.0",
|
|
42
|
+
"@kustodian/loader": "1.1.0",
|
|
43
|
+
"@kustodian/nodes": "1.1.0",
|
|
44
|
+
"@kustodian/plugin-k0s": "1.1.0",
|
|
45
|
+
"@kustodian/plugins": "1.0.1",
|
|
46
|
+
"@kustodian/registry": "1.1.0",
|
|
47
|
+
"@kustodian/schema": "1.3.0",
|
|
48
|
+
"@kustodian/sources": "1.3.0",
|
|
49
49
|
"ora": "^9.0.0",
|
|
50
50
|
"yaml": "^2.8.2"
|
|
51
51
|
}
|
package/src/bin.ts
CHANGED
|
File without changes
|
package/src/commands/apply.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
3
|
import { is_success, success } from '@kustodian/core';
|
|
4
|
+
import { validate_template_requirements } from '@kustodian/generator';
|
|
4
5
|
import { find_project_root, load_project } from '@kustodian/loader';
|
|
5
6
|
import type { NodeListType } from '@kustodian/nodes';
|
|
6
7
|
import type { ClusterType } from '@kustodian/schema';
|
|
@@ -278,6 +279,38 @@ export const apply_command = define_command({
|
|
|
278
279
|
if (!skip_templates) {
|
|
279
280
|
console.log('\n[4/4] Deploying templates...');
|
|
280
281
|
|
|
282
|
+
// Validate template requirements
|
|
283
|
+
console.log(' → Validating template requirements...');
|
|
284
|
+
const enabled_template_refs =
|
|
285
|
+
loaded_cluster.cluster.spec.templates?.filter((t) => t.enabled !== false) || [];
|
|
286
|
+
|
|
287
|
+
if (enabled_template_refs.length > 0) {
|
|
288
|
+
const enabled_templates = project.templates
|
|
289
|
+
.filter((t) => enabled_template_refs.some((ref) => ref.name === t.template.metadata.name))
|
|
290
|
+
.map((t) => t.template);
|
|
291
|
+
|
|
292
|
+
const requirements_result = validate_template_requirements(
|
|
293
|
+
enabled_templates,
|
|
294
|
+
loaded_cluster.nodes,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (!requirements_result.valid) {
|
|
298
|
+
console.error(' ✗ Template requirement validation failed:');
|
|
299
|
+
for (const error of requirements_result.errors) {
|
|
300
|
+
console.error(` - ${error.template}: ${error.message}`);
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
success: false as const,
|
|
304
|
+
error: {
|
|
305
|
+
code: 'REQUIREMENT_VALIDATION_ERROR',
|
|
306
|
+
message: 'Template requirements not met',
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(' ✓ All template requirements satisfied');
|
|
312
|
+
}
|
|
313
|
+
|
|
281
314
|
if (loaded_cluster.cluster.spec.oci) {
|
|
282
315
|
// OCI Mode - generate in memory and apply directly
|
|
283
316
|
console.log(' → Cluster uses OCI deployment');
|
package/src/commands/update.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
|
|
3
|
-
import { failure, is_success, success } from '@kustodian/core';
|
|
3
|
+
import { type ResultType, failure, is_success, success } from '@kustodian/core';
|
|
4
|
+
import type { KustodianErrorType } from '@kustodian/core';
|
|
4
5
|
import { read_yaml_file, write_yaml_file } from '@kustodian/loader';
|
|
5
6
|
import { find_project_root, load_project } from '@kustodian/loader';
|
|
6
7
|
import {
|
|
8
|
+
type ImageReferenceType,
|
|
9
|
+
type RegistryClientType,
|
|
10
|
+
type TagInfoType,
|
|
7
11
|
check_version_update,
|
|
8
12
|
create_client_for_image,
|
|
13
|
+
create_helm_client,
|
|
9
14
|
filter_semver_tags,
|
|
10
15
|
parse_image_reference,
|
|
11
16
|
} from '@kustodian/registry';
|
|
12
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
type HelmSubstitutionType,
|
|
19
|
+
type VersionSubstitutionType,
|
|
20
|
+
is_helm_substitution,
|
|
21
|
+
is_version_substitution,
|
|
22
|
+
} from '@kustodian/schema';
|
|
13
23
|
|
|
14
24
|
import { define_command } from '../command.js';
|
|
15
25
|
|
|
@@ -20,7 +30,8 @@ interface UpdateResultType {
|
|
|
20
30
|
cluster: string;
|
|
21
31
|
template: string;
|
|
22
32
|
substitution: string;
|
|
23
|
-
|
|
33
|
+
source: string; // Image name or Helm chart reference
|
|
34
|
+
source_type: 'image' | 'helm';
|
|
24
35
|
current: string;
|
|
25
36
|
latest: string;
|
|
26
37
|
constraint?: string | undefined;
|
|
@@ -96,12 +107,13 @@ export const update_command = define_command({
|
|
|
96
107
|
});
|
|
97
108
|
}
|
|
98
109
|
|
|
99
|
-
// Collect all version substitutions from templates enabled in this cluster
|
|
110
|
+
// Collect all version and helm substitutions from templates enabled in this cluster
|
|
100
111
|
const version_subs: Array<{
|
|
101
112
|
template_name: string;
|
|
102
113
|
kustomization_name: string;
|
|
103
|
-
substitution: VersionSubstitutionType;
|
|
114
|
+
substitution: VersionSubstitutionType | HelmSubstitutionType;
|
|
104
115
|
current_value: string | undefined;
|
|
116
|
+
type: 'version' | 'helm';
|
|
105
117
|
}> = [];
|
|
106
118
|
|
|
107
119
|
for (const loaded_template of project.templates) {
|
|
@@ -127,6 +139,19 @@ export const update_command = define_command({
|
|
|
127
139
|
kustomization_name: kustomization.name,
|
|
128
140
|
substitution: sub,
|
|
129
141
|
current_value: template_config?.values?.[sub.name] ?? sub.default,
|
|
142
|
+
type: 'version',
|
|
143
|
+
});
|
|
144
|
+
} else if (is_helm_substitution(sub)) {
|
|
145
|
+
if (substitution_filter && sub.name !== substitution_filter) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
version_subs.push({
|
|
150
|
+
template_name: template.metadata.name,
|
|
151
|
+
kustomization_name: kustomization.name,
|
|
152
|
+
substitution: sub,
|
|
153
|
+
current_value: template_config?.values?.[sub.name] ?? sub.default,
|
|
154
|
+
type: 'helm',
|
|
130
155
|
});
|
|
131
156
|
}
|
|
132
157
|
}
|
|
@@ -146,19 +171,53 @@ export const update_command = define_command({
|
|
|
146
171
|
console.log(`Found ${version_subs.length} version substitution(s) to check\n`);
|
|
147
172
|
}
|
|
148
173
|
|
|
149
|
-
// Check each version substitution
|
|
174
|
+
// Check each version and helm substitution
|
|
150
175
|
const results: UpdateResultType[] = [];
|
|
151
176
|
const updates_to_apply: Map<string, Record<string, string>> = new Map();
|
|
152
177
|
|
|
153
|
-
for (const { template_name, substitution, current_value } of version_subs) {
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
for (const { template_name, substitution, current_value, type } of version_subs) {
|
|
179
|
+
let source_name: string;
|
|
180
|
+
let client: RegistryClientType;
|
|
181
|
+
|
|
182
|
+
if (type === 'version') {
|
|
183
|
+
const version_sub = substitution as VersionSubstitutionType;
|
|
184
|
+
const image_ref = parse_image_reference(version_sub.registry.image);
|
|
185
|
+
source_name = version_sub.registry.image;
|
|
186
|
+
client = create_client_for_image(image_ref);
|
|
187
|
+
} else {
|
|
188
|
+
// helm type
|
|
189
|
+
const helm_sub = substitution as HelmSubstitutionType;
|
|
190
|
+
source_name = helm_sub.helm.oci || helm_sub.helm.repository || '';
|
|
191
|
+
source_name = `${source_name}/${helm_sub.helm.chart}`;
|
|
192
|
+
// Create helm config object to satisfy exactOptionalPropertyTypes
|
|
193
|
+
const helm_config: { repository?: string; oci?: string; chart: string } = {
|
|
194
|
+
chart: helm_sub.helm.chart,
|
|
195
|
+
};
|
|
196
|
+
if (helm_sub.helm.repository) {
|
|
197
|
+
helm_config.repository = helm_sub.helm.repository;
|
|
198
|
+
}
|
|
199
|
+
if (helm_sub.helm.oci) {
|
|
200
|
+
helm_config.oci = helm_sub.helm.oci;
|
|
201
|
+
}
|
|
202
|
+
client = create_helm_client(helm_config);
|
|
203
|
+
}
|
|
156
204
|
|
|
157
205
|
if (!json_output) {
|
|
158
|
-
console.log(`Checking ${substitution.name} (${
|
|
206
|
+
console.log(`Checking ${substitution.name} (${source_name})...`);
|
|
159
207
|
}
|
|
160
208
|
|
|
161
|
-
|
|
209
|
+
// For version type, we need to pass the image reference
|
|
210
|
+
// For helm type, the parameter is optional and ignored
|
|
211
|
+
let tags_result: ResultType<TagInfoType[], KustodianErrorType>;
|
|
212
|
+
if (type === 'version') {
|
|
213
|
+
tags_result = await client.list_tags(parse_image_reference(source_name));
|
|
214
|
+
} else {
|
|
215
|
+
// Helm client - the list_tags parameter is optional for helm clients
|
|
216
|
+
type ListTagsFn = (
|
|
217
|
+
ref?: ImageReferenceType,
|
|
218
|
+
) => Promise<ResultType<TagInfoType[], KustodianErrorType>>;
|
|
219
|
+
tags_result = await (client.list_tags as ListTagsFn)(undefined);
|
|
220
|
+
}
|
|
162
221
|
if (!is_success(tags_result)) {
|
|
163
222
|
if (!json_output) {
|
|
164
223
|
console.error(` Failed to fetch tags: ${tags_result.error.message}`);
|
|
@@ -184,7 +243,8 @@ export const update_command = define_command({
|
|
|
184
243
|
cluster: cluster_name,
|
|
185
244
|
template: template_name,
|
|
186
245
|
substitution: substitution.name,
|
|
187
|
-
|
|
246
|
+
source: source_name,
|
|
247
|
+
source_type: type === 'version' ? 'image' : 'helm',
|
|
188
248
|
current: check.current_version,
|
|
189
249
|
latest: check.latest_version,
|
|
190
250
|
constraint: substitution.constraint,
|
package/src/commands/validate.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { failure, success } from '@kustodian/core';
|
|
2
|
-
import { validate_dependency_graph } from '@kustodian/generator';
|
|
2
|
+
import { validate_dependency_graph, validate_template_requirements } from '@kustodian/generator';
|
|
3
3
|
import { find_project_root, load_project } from '@kustodian/loader';
|
|
4
4
|
|
|
5
5
|
import { define_command } from '../command.js';
|
|
@@ -82,6 +82,46 @@ export const validate_command = define_command({
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Validate template requirements for each cluster
|
|
86
|
+
console.log('\nValidating template requirements...');
|
|
87
|
+
let has_requirement_errors = false;
|
|
88
|
+
|
|
89
|
+
for (const cluster_data of clusters) {
|
|
90
|
+
const cluster_name = cluster_data.cluster.metadata.name;
|
|
91
|
+
const enabled_template_refs =
|
|
92
|
+
cluster_data.cluster.spec.templates?.filter((t) => t.enabled !== false) || [];
|
|
93
|
+
|
|
94
|
+
if (enabled_template_refs.length === 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get enabled templates
|
|
99
|
+
const enabled_templates = project.templates
|
|
100
|
+
.filter((t) => enabled_template_refs.some((ref) => ref.name === t.template.metadata.name))
|
|
101
|
+
.map((t) => t.template);
|
|
102
|
+
|
|
103
|
+
// Validate requirements
|
|
104
|
+
const requirements_result = validate_template_requirements(
|
|
105
|
+
enabled_templates,
|
|
106
|
+
cluster_data.nodes,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!requirements_result.valid) {
|
|
110
|
+
has_requirement_errors = true;
|
|
111
|
+
console.error(`\nRequirement validation errors for cluster '${cluster_name}':`);
|
|
112
|
+
for (const error of requirements_result.errors) {
|
|
113
|
+
console.error(` ✗ ${error.template}: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (has_requirement_errors) {
|
|
119
|
+
return failure({
|
|
120
|
+
code: 'REQUIREMENT_VALIDATION_ERROR',
|
|
121
|
+
message: 'Template requirement validation failed',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
85
125
|
// Validate dependency graph
|
|
86
126
|
console.log('\nValidating dependency graph...');
|
|
87
127
|
const templates = project.templates.map((t) => t.template);
|