@kustodian/schema 1.1.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.1.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
@@ -21,10 +21,27 @@ export const health_check_schema = z.object({
21
21
  kind: z.string().min(1),
22
22
  name: z.string().min(1),
23
23
  namespace: z.string().min(1).optional(),
24
+ api_version: z.string().min(1).optional(),
24
25
  });
25
26
 
26
27
  export type HealthCheckType = z.infer<typeof health_check_schema>;
27
28
 
29
+ /**
30
+ * Health check expression configuration using CEL (Common Expression Language).
31
+ * Supports custom health check conditions via CEL expressions.
32
+ */
33
+ export const health_check_expr_schema = z.object({
34
+ api_version: z.string().min(1),
35
+ kind: z.string().min(1),
36
+ namespace: z.string().min(1).optional(),
37
+ /** CEL expression for when resource is healthy/current */
38
+ current: z.string().min(1).optional(),
39
+ /** CEL expression for when resource has failed */
40
+ failed: z.string().min(1).optional(),
41
+ });
42
+
43
+ export type HealthCheckExprType = z.infer<typeof health_check_expr_schema>;
44
+
28
45
  /**
29
46
  * Registry configuration for version substitutions.
30
47
  */
@@ -37,6 +54,31 @@ export const registry_config_schema = z.object({
37
54
 
38
55
  export type RegistryConfigType = z.infer<typeof registry_config_schema>;
39
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
+
40
82
  /**
41
83
  * Generic substitution (backward compatible, default type).
42
84
  */
@@ -45,6 +87,7 @@ export const generic_substitution_schema = z.object({
45
87
  name: z.string().min(1),
46
88
  default: z.string().optional(),
47
89
  secret: z.string().optional(),
90
+ preserve_case: z.boolean().optional(),
48
91
  });
49
92
 
50
93
  export type GenericSubstitutionType = z.infer<typeof generic_substitution_schema>;
@@ -68,6 +111,25 @@ export const version_substitution_schema = z.object({
68
111
 
69
112
  export type VersionSubstitutionType = z.infer<typeof version_substitution_schema>;
70
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
+
71
133
  /**
72
134
  * Namespace substitution with Kubernetes naming validation.
73
135
  */
@@ -81,29 +143,46 @@ export type NamespaceSubstitutionType = z.infer<typeof namespace_substitution_sc
81
143
 
82
144
  /**
83
145
  * 1Password substitution for fetching secrets from 1Password vaults.
84
- * Uses the op:// secret reference format.
146
+ * Uses the op:// secret reference format, or shorthand with cluster defaults.
85
147
  */
86
- export const onepassword_substitution_schema = z.object({
87
- type: z.literal('1password'),
88
- name: z.string().min(1),
89
- /** 1Password secret reference: op://vault/item[/section]/field */
90
- ref: z.string().min(1),
91
- /** Optional default value if secret cannot be fetched */
92
- default: z.string().optional(),
93
- });
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
+ );
94
172
 
95
173
  export type OnePasswordSubstitutionType = z.infer<typeof onepassword_substitution_schema>;
96
174
 
97
175
  /**
98
176
  * Doppler substitution for fetching secrets from Doppler projects.
177
+ * Project and config can be omitted if configured at cluster level.
99
178
  */
100
179
  export const doppler_substitution_schema = z.object({
101
180
  type: z.literal('doppler'),
102
181
  name: z.string().min(1),
103
- /** Doppler project name */
104
- project: z.string().min(1),
105
- /** Doppler config name (e.g., 'dev', 'stg', 'prd') */
106
- 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(),
107
186
  /** Secret key name in Doppler */
108
187
  secret: z.string().min(1),
109
188
  /** Optional default value if secret cannot be fetched */
@@ -118,6 +197,7 @@ export type DopplerSubstitutionType = z.infer<typeof doppler_substitution_schema
118
197
  */
119
198
  export const substitution_schema = z.union([
120
199
  version_substitution_schema,
200
+ helm_substitution_schema,
121
201
  namespace_substitution_schema,
122
202
  onepassword_substitution_schema,
123
203
  doppler_substitution_schema,
@@ -133,6 +213,13 @@ export function is_version_substitution(sub: SubstitutionType): sub is VersionSu
133
213
  return 'type' in sub && sub.type === 'version';
134
214
  }
135
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
+
136
223
  /**
137
224
  * Type guard for namespace substitutions.
138
225
  */
package/src/template.ts CHANGED
@@ -2,12 +2,59 @@ import { z } from 'zod';
2
2
 
3
3
  import {
4
4
  api_version_schema,
5
+ health_check_expr_schema,
5
6
  health_check_schema,
6
7
  metadata_schema,
7
8
  namespace_config_schema,
8
9
  substitution_schema,
9
10
  } from './common.js';
10
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
+
11
58
  /**
12
59
  * A single kustomization within a template.
13
60
  * Maps to a Flux Kustomization resource.
@@ -16,21 +63,47 @@ export const kustomization_schema = z.object({
16
63
  name: z.string().min(1),
17
64
  path: z.string().min(1),
18
65
  namespace: namespace_config_schema.optional(),
19
- depends_on: z.array(z.string()).optional(),
66
+ depends_on: z.array(dependency_ref_schema).optional(),
20
67
  substitutions: z.array(substitution_schema).optional(),
21
68
  health_checks: z.array(health_check_schema).optional(),
69
+ health_check_exprs: z.array(health_check_expr_schema).optional(),
22
70
  prune: z.boolean().optional().default(true),
23
71
  wait: z.boolean().optional().default(true),
24
72
  timeout: z.string().optional(),
25
73
  retry_interval: z.string().optional(),
74
+ enabled: z.boolean().optional().default(true),
75
+ preservation: preservation_policy_schema.optional(),
26
76
  });
27
77
 
28
78
  export type KustomizationType = z.infer<typeof kustomization_schema>;
29
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
+
30
102
  /**
31
103
  * Template specification containing kustomizations.
32
104
  */
33
105
  export const template_spec_schema = z.object({
106
+ requirements: z.array(template_requirement_schema).optional(),
34
107
  kustomizations: z.array(kustomization_schema).min(1),
35
108
  });
36
109