@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kustodian/cli",
3
- "version": "1.1.0",
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": "workspace:*",
41
- "@kustodian/generator": "workspace:*",
42
- "@kustodian/loader": "workspace:*",
43
- "@kustodian/nodes": "workspace:*",
44
- "@kustodian/plugin-k0s": "workspace:*",
45
- "@kustodian/plugins": "workspace:*",
46
- "@kustodian/registry": "workspace:*",
47
- "@kustodian/schema": "workspace:*",
48
- "@kustodian/sources": "workspace:*",
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
@@ -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');
@@ -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 { type VersionSubstitutionType, is_version_substitution } from '@kustodian/schema';
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
- image: string;
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
- const image_ref = parse_image_reference(substitution.registry.image);
155
- const client = create_client_for_image(image_ref);
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} (${substitution.registry.image})...`);
206
+ console.log(`Checking ${substitution.name} (${source_name})...`);
159
207
  }
160
208
 
161
- const tags_result = await client.list_tags(image_ref);
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
- image: substitution.registry.image,
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,
@@ -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);