@kustodian/schema 1.2.0 → 1.3.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/schema",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "JSON Schema definitions for Kustodian YAML validation",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -37,6 +37,5 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "zod": "^3.25.30"
40
- },
41
- "devDependencies": {}
40
+ }
42
41
  }
package/src/cluster.ts CHANGED
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
 
3
3
  import { api_version_schema, metadata_schema, values_schema } from './common.js';
4
4
  import { ssh_config_schema } from './node-list.js';
5
+ import { preservation_mode_schema } from './template.js';
5
6
 
6
7
  /**
7
8
  * Git repository configuration for a cluster.
@@ -30,6 +31,22 @@ export const oci_config_schema = z.object({
30
31
 
31
32
  export type OciConfigType = z.infer<typeof oci_config_schema>;
32
33
 
34
+ /**
35
+ * Kustomization override configuration within a cluster.
36
+ *
37
+ * Allows overriding kustomization enablement and preservation from template defaults.
38
+ */
39
+ export const kustomization_override_schema = z.object({
40
+ enabled: z.boolean(),
41
+ preservation: z
42
+ .object({
43
+ mode: preservation_mode_schema,
44
+ })
45
+ .optional(),
46
+ });
47
+
48
+ export type KustomizationOverrideType = z.infer<typeof kustomization_override_schema>;
49
+
33
50
  /**
34
51
  * Template enablement configuration within a cluster.
35
52
  */
@@ -37,6 +54,15 @@ export const template_config_schema = z.object({
37
54
  name: z.string().min(1),
38
55
  enabled: z.boolean().optional().default(true),
39
56
  values: values_schema.optional(),
57
+ kustomizations: z
58
+ .record(
59
+ z.string(), // kustomization name
60
+ z.union([
61
+ z.boolean(), // Simple: just enabled/disabled
62
+ kustomization_override_schema, // Advanced: with preservation
63
+ ]),
64
+ )
65
+ .optional(),
40
66
  });
41
67
 
42
68
  export type TemplateConfigType = z.infer<typeof template_config_schema>;
@@ -72,6 +98,56 @@ export const github_config_schema = z.object({
72
98
 
73
99
  export type GithubConfigType = z.infer<typeof github_config_schema>;
74
100
 
101
+ /**
102
+ * Bootstrap credential configuration for secret providers.
103
+ * Allows obtaining credentials from another secret provider.
104
+ */
105
+ export const bootstrap_credential_schema = z.discriminatedUnion('type', [
106
+ z.object({
107
+ type: z.literal('1password'),
108
+ ref: z.string().min(1),
109
+ }),
110
+ z.object({
111
+ type: z.literal('doppler'),
112
+ project: z.string().min(1),
113
+ config: z.string().min(1),
114
+ secret: z.string().min(1),
115
+ }),
116
+ ]);
117
+
118
+ export type BootstrapCredentialType = z.infer<typeof bootstrap_credential_schema>;
119
+
120
+ /**
121
+ * Doppler secret provider configuration at cluster level.
122
+ */
123
+ export const doppler_config_schema = z.object({
124
+ project: z.string().min(1),
125
+ config: z.string().min(1),
126
+ service_token: bootstrap_credential_schema.optional(),
127
+ });
128
+
129
+ export type DopplerConfigType = z.infer<typeof doppler_config_schema>;
130
+
131
+ /**
132
+ * 1Password secret provider configuration at cluster level.
133
+ */
134
+ export const onepassword_config_schema = z.object({
135
+ vault: z.string().min(1),
136
+ service_account_token: bootstrap_credential_schema.optional(),
137
+ });
138
+
139
+ export type OnePasswordConfigType = z.infer<typeof onepassword_config_schema>;
140
+
141
+ /**
142
+ * Secret providers configuration at cluster level.
143
+ */
144
+ export const secrets_config_schema = z.object({
145
+ doppler: doppler_config_schema.optional(),
146
+ onepassword: onepassword_config_schema.optional(),
147
+ });
148
+
149
+ export type SecretsConfigType = z.infer<typeof secrets_config_schema>;
150
+
75
151
  /**
76
152
  * Cluster specification.
77
153
  */
@@ -86,6 +162,7 @@ export const cluster_spec_schema = z
86
162
  plugins: z.array(plugin_config_schema).optional(),
87
163
  node_defaults: node_defaults_schema.optional(),
88
164
  nodes: z.array(z.string()).optional(),
165
+ secrets: secrets_config_schema.optional(),
89
166
  })
90
167
  .refine((data) => data.git || data.oci, {
91
168
  message: "Either 'git' or 'oci' must be specified",
package/src/common.ts CHANGED
@@ -54,6 +54,31 @@ export const registry_config_schema = z.object({
54
54
 
55
55
  export type RegistryConfigType = z.infer<typeof registry_config_schema>;
56
56
 
57
+ /**
58
+ * Helm repository configuration for helm chart version substitutions.
59
+ * Supports both traditional Helm repositories and OCI registries.
60
+ */
61
+ export const helm_config_schema = z
62
+ .object({
63
+ /** Helm chart repository URL (e.g., https://traefik.github.io/charts) */
64
+ repository: z.string().url().optional(),
65
+ /** OCI registry URL for Helm charts (e.g., oci://ghcr.io/traefik/helm) */
66
+ oci: z.string().startsWith('oci://').optional(),
67
+ /** Chart name */
68
+ chart: z.string().min(1),
69
+ })
70
+ .refine(
71
+ (data) => {
72
+ // Either repository or oci must be provided
73
+ return data.repository !== undefined || data.oci !== undefined;
74
+ },
75
+ {
76
+ message: "Either 'repository' or 'oci' must be specified",
77
+ },
78
+ );
79
+
80
+ export type HelmConfigType = z.infer<typeof helm_config_schema>;
81
+
57
82
  /**
58
83
  * Generic substitution (backward compatible, default type).
59
84
  */
@@ -86,6 +111,25 @@ export const version_substitution_schema = z.object({
86
111
 
87
112
  export type VersionSubstitutionType = z.infer<typeof version_substitution_schema>;
88
113
 
114
+ /**
115
+ * Helm chart version substitution for tracking Helm chart versions.
116
+ */
117
+ export const helm_substitution_schema = z.object({
118
+ type: z.literal('helm'),
119
+ name: z.string().min(1),
120
+ default: z.string().optional(),
121
+ /** Semver constraint: ^1.0.0, ~2.3.0, >=1.0.0 <2.0.0 */
122
+ constraint: z.string().optional(),
123
+ /** Helm repository configuration for fetching available chart versions */
124
+ helm: helm_config_schema,
125
+ /** Regex pattern for filtering valid tags (default: semver-like) */
126
+ tag_pattern: z.string().optional(),
127
+ /** Exclude pre-release versions (default: true) */
128
+ exclude_prerelease: z.boolean().optional(),
129
+ });
130
+
131
+ export type HelmSubstitutionType = z.infer<typeof helm_substitution_schema>;
132
+
89
133
  /**
90
134
  * Namespace substitution with Kubernetes naming validation.
91
135
  */
@@ -99,29 +143,46 @@ export type NamespaceSubstitutionType = z.infer<typeof namespace_substitution_sc
99
143
 
100
144
  /**
101
145
  * 1Password substitution for fetching secrets from 1Password vaults.
102
- * Uses the op:// secret reference format.
146
+ * Uses the op:// secret reference format, or shorthand with cluster defaults.
103
147
  */
104
- export const onepassword_substitution_schema = z.object({
105
- type: z.literal('1password'),
106
- name: z.string().min(1),
107
- /** 1Password secret reference: op://vault/item[/section]/field */
108
- ref: z.string().min(1),
109
- /** Optional default value if secret cannot be fetched */
110
- default: z.string().optional(),
111
- });
148
+ export const onepassword_substitution_schema = z
149
+ .object({
150
+ type: z.literal('1password'),
151
+ name: z.string().min(1),
152
+ /** 1Password secret reference: op://vault/item[/section]/field, or shorthand item/field when vault is configured at cluster level */
153
+ ref: z.string().min(1).optional(),
154
+ /** Item name (shorthand, requires cluster-level vault configuration) */
155
+ item: z.string().min(1).optional(),
156
+ /** Field name (shorthand, requires cluster-level vault configuration) */
157
+ field: z.string().min(1).optional(),
158
+ /** Section name (optional, for shorthand references) */
159
+ section: z.string().optional(),
160
+ /** Optional default value if secret cannot be fetched */
161
+ default: z.string().optional(),
162
+ })
163
+ .refine(
164
+ (data) => {
165
+ // Either ref must be provided, or both item and field
166
+ return data.ref !== undefined || (data.item !== undefined && data.field !== undefined);
167
+ },
168
+ {
169
+ message: "Either 'ref' or both 'item' and 'field' must be specified",
170
+ },
171
+ );
112
172
 
113
173
  export type OnePasswordSubstitutionType = z.infer<typeof onepassword_substitution_schema>;
114
174
 
115
175
  /**
116
176
  * Doppler substitution for fetching secrets from Doppler projects.
177
+ * Project and config can be omitted if configured at cluster level.
117
178
  */
118
179
  export const doppler_substitution_schema = z.object({
119
180
  type: z.literal('doppler'),
120
181
  name: z.string().min(1),
121
- /** Doppler project name */
122
- project: z.string().min(1),
123
- /** Doppler config name (e.g., 'dev', 'stg', 'prd') */
124
- config: z.string().min(1),
182
+ /** Doppler project name (optional if configured at cluster level) */
183
+ project: z.string().min(1).optional(),
184
+ /** Doppler config name (optional if configured at cluster level, e.g., 'dev', 'stg', 'prd') */
185
+ config: z.string().min(1).optional(),
125
186
  /** Secret key name in Doppler */
126
187
  secret: z.string().min(1),
127
188
  /** Optional default value if secret cannot be fetched */
@@ -136,6 +197,7 @@ export type DopplerSubstitutionType = z.infer<typeof doppler_substitution_schema
136
197
  */
137
198
  export const substitution_schema = z.union([
138
199
  version_substitution_schema,
200
+ helm_substitution_schema,
139
201
  namespace_substitution_schema,
140
202
  onepassword_substitution_schema,
141
203
  doppler_substitution_schema,
@@ -151,6 +213,13 @@ export function is_version_substitution(sub: SubstitutionType): sub is VersionSu
151
213
  return 'type' in sub && sub.type === 'version';
152
214
  }
153
215
 
216
+ /**
217
+ * Type guard for helm substitutions.
218
+ */
219
+ export function is_helm_substitution(sub: SubstitutionType): sub is HelmSubstitutionType {
220
+ return 'type' in sub && sub.type === 'helm';
221
+ }
222
+
154
223
  /**
155
224
  * Type guard for namespace substitutions.
156
225
  */
package/src/template.ts CHANGED
@@ -9,6 +9,52 @@ import {
9
9
  substitution_schema,
10
10
  } from './common.js';
11
11
 
12
+ /**
13
+ * Raw dependency reference to an external Flux Kustomization.
14
+ * Used for dependencies outside the kustodian-generated system.
15
+ */
16
+ export const raw_dependency_ref_schema = z.object({
17
+ raw: z.object({
18
+ name: z.string().min(1),
19
+ namespace: z.string().min(1),
20
+ }),
21
+ });
22
+
23
+ export type RawDependencyRefType = z.infer<typeof raw_dependency_ref_schema>;
24
+
25
+ /**
26
+ * Dependency reference - either a string or a raw reference object.
27
+ *
28
+ * Supports three formats:
29
+ * - Within-template: `database`
30
+ * - Cross-template: `secrets/doppler`
31
+ * - Raw external: `{ raw: { name: 'legacy-infrastructure', namespace: 'gitops-system' } }`
32
+ */
33
+ export const dependency_ref_schema = z.union([z.string(), raw_dependency_ref_schema]);
34
+
35
+ export type DependencyRefType = z.infer<typeof dependency_ref_schema>;
36
+
37
+ /**
38
+ * Preservation mode for disabled kustomizations.
39
+ *
40
+ * - none: Delete all resources when disabled
41
+ * - stateful: Keep PVCs, Secrets, and ConfigMaps (default, safe)
42
+ * - custom: Keep only specified resource types
43
+ */
44
+ export const preservation_mode_schema = z.enum(['none', 'stateful', 'custom']);
45
+
46
+ export type PreservationModeType = z.infer<typeof preservation_mode_schema>;
47
+
48
+ /**
49
+ * Preservation policy for a kustomization when disabled.
50
+ */
51
+ export const preservation_policy_schema = z.object({
52
+ mode: preservation_mode_schema.default('stateful'),
53
+ keep_resources: z.array(z.string()).optional(),
54
+ });
55
+
56
+ export type PreservationPolicyType = z.infer<typeof preservation_policy_schema>;
57
+
12
58
  /**
13
59
  * A single kustomization within a template.
14
60
  * Maps to a Flux Kustomization resource.
@@ -17,7 +63,7 @@ export const kustomization_schema = z.object({
17
63
  name: z.string().min(1),
18
64
  path: z.string().min(1),
19
65
  namespace: namespace_config_schema.optional(),
20
- depends_on: z.array(z.string()).optional(),
66
+ depends_on: z.array(dependency_ref_schema).optional(),
21
67
  substitutions: z.array(substitution_schema).optional(),
22
68
  health_checks: z.array(health_check_schema).optional(),
23
69
  health_check_exprs: z.array(health_check_expr_schema).optional(),
@@ -25,14 +71,39 @@ export const kustomization_schema = z.object({
25
71
  wait: z.boolean().optional().default(true),
26
72
  timeout: z.string().optional(),
27
73
  retry_interval: z.string().optional(),
74
+ enabled: z.boolean().optional().default(true),
75
+ preservation: preservation_policy_schema.optional(),
28
76
  });
29
77
 
30
78
  export type KustomizationType = z.infer<typeof kustomization_schema>;
31
79
 
80
+ /**
81
+ * Node label requirement - requires specific labels to be present on cluster nodes.
82
+ */
83
+ export const node_label_requirement_schema = z.object({
84
+ type: z.literal('nodeLabel'),
85
+ key: z.string().min(1),
86
+ value: z.string().optional(),
87
+ atLeast: z.number().int().positive().default(1),
88
+ });
89
+
90
+ export type NodeLabelRequirementType = z.infer<typeof node_label_requirement_schema>;
91
+
92
+ /**
93
+ * Template requirement - validates cluster prerequisites before deployment.
94
+ * Currently supports node label requirements, extensible for future types.
95
+ */
96
+ export const template_requirement_schema = z.discriminatedUnion('type', [
97
+ node_label_requirement_schema,
98
+ ]);
99
+
100
+ export type TemplateRequirementType = z.infer<typeof template_requirement_schema>;
101
+
32
102
  /**
33
103
  * Template specification containing kustomizations.
34
104
  */
35
105
  export const template_spec_schema = z.object({
106
+ requirements: z.array(template_requirement_schema).optional(),
36
107
  kustomizations: z.array(kustomization_schema).min(1),
37
108
  });
38
109