@kustodian/schema 1.0.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 ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kustodian/schema",
3
+ "version": "1.0.0",
4
+ "description": "JSON Schema definitions for Kustodian YAML validation",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.ts",
11
+ "import": "./src/index.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "src"
16
+ ],
17
+ "scripts": {
18
+ "test": "bun test",
19
+ "test:watch": "bun test --watch",
20
+ "typecheck": "bun run tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "kustodian",
24
+ "schema",
25
+ "validation",
26
+ "zod"
27
+ ],
28
+ "author": "Luca Silverentand <luca@onezero.company>",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/lucasilverentand/kustodian.git",
33
+ "directory": "packages/schema"
34
+ },
35
+ "publishConfig": {
36
+ "registry": "https://npm.pkg.github.com"
37
+ },
38
+ "dependencies": {
39
+ "zod": "^3.25.30"
40
+ },
41
+ "devDependencies": {}
42
+ }
package/src/cluster.ts ADDED
@@ -0,0 +1,113 @@
1
+ import { z } from 'zod';
2
+
3
+ import { api_version_schema, metadata_schema, values_schema } from './common.js';
4
+ import { ssh_config_schema } from './node-list.js';
5
+
6
+ /**
7
+ * Git repository configuration for a cluster.
8
+ */
9
+ export const git_config_schema = z.object({
10
+ owner: z.string().min(1),
11
+ repository: z.string().min(1),
12
+ branch: z.string().min(1).optional().default('main'),
13
+ path: z.string().optional(),
14
+ });
15
+
16
+ export type GitConfigType = z.infer<typeof git_config_schema>;
17
+
18
+ /**
19
+ * OCI repository configuration for a cluster.
20
+ */
21
+ export const oci_config_schema = z.object({
22
+ registry: z.string().min(1),
23
+ repository: z.string().min(1),
24
+ tag_strategy: z.enum(['cluster', 'git-sha', 'version', 'manual']).optional().default('git-sha'),
25
+ tag: z.string().optional(),
26
+ secret_ref: z.string().optional(),
27
+ provider: z.enum(['aws', 'azure', 'gcp', 'generic']).optional().default('generic'),
28
+ insecure: z.boolean().optional().default(false),
29
+ });
30
+
31
+ export type OciConfigType = z.infer<typeof oci_config_schema>;
32
+
33
+ /**
34
+ * Template enablement configuration within a cluster.
35
+ */
36
+ export const template_config_schema = z.object({
37
+ name: z.string().min(1),
38
+ enabled: z.boolean().optional().default(true),
39
+ values: values_schema.optional(),
40
+ });
41
+
42
+ export type TemplateConfigType = z.infer<typeof template_config_schema>;
43
+
44
+ /**
45
+ * Plugin configuration within a cluster.
46
+ */
47
+ export const plugin_config_schema = z.object({
48
+ name: z.string().min(1),
49
+ config: z.record(z.string(), z.unknown()).optional(),
50
+ });
51
+
52
+ export type PluginConfigType = z.infer<typeof plugin_config_schema>;
53
+
54
+ /**
55
+ * Node defaults configuration within a cluster.
56
+ */
57
+ export const node_defaults_schema = z.object({
58
+ label_prefix: z.string().optional(),
59
+ ssh: ssh_config_schema.optional(),
60
+ });
61
+
62
+ export type NodeDefaultsType = z.infer<typeof node_defaults_schema>;
63
+
64
+ /**
65
+ * GitHub repository configuration for GitOps metadata.
66
+ */
67
+ export const github_config_schema = z.object({
68
+ organization: z.string().min(1),
69
+ repository: z.string().min(1),
70
+ branch: z.string().min(1).optional().default('main'),
71
+ });
72
+
73
+ export type GithubConfigType = z.infer<typeof github_config_schema>;
74
+
75
+ /**
76
+ * Cluster specification.
77
+ */
78
+ export const cluster_spec_schema = z
79
+ .object({
80
+ code: z.string().min(1).optional(),
81
+ domain: z.string().min(1),
82
+ git: git_config_schema.optional(),
83
+ oci: oci_config_schema.optional(),
84
+ github: github_config_schema.optional(),
85
+ templates: z.array(template_config_schema).optional(),
86
+ plugins: z.array(plugin_config_schema).optional(),
87
+ node_defaults: node_defaults_schema.optional(),
88
+ nodes: z.array(z.string()).optional(),
89
+ })
90
+ .refine((data) => data.git || data.oci, {
91
+ message: "Either 'git' or 'oci' must be specified",
92
+ });
93
+
94
+ export type ClusterSpecType = z.infer<typeof cluster_spec_schema>;
95
+
96
+ /**
97
+ * Complete Cluster resource definition.
98
+ */
99
+ export const cluster_schema = z.object({
100
+ apiVersion: api_version_schema,
101
+ kind: z.literal('Cluster'),
102
+ metadata: metadata_schema,
103
+ spec: cluster_spec_schema,
104
+ });
105
+
106
+ export type ClusterType = z.infer<typeof cluster_schema>;
107
+
108
+ /**
109
+ * Validates a cluster object and returns the result.
110
+ */
111
+ export function validate_cluster(data: unknown): z.SafeParseReturnType<unknown, ClusterType> {
112
+ return cluster_schema.safeParse(data);
113
+ }
package/src/common.ts ADDED
@@ -0,0 +1,181 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Common API version for all Kustodian resources.
5
+ */
6
+ export const api_version_schema = z.literal('kustodian.io/v1');
7
+
8
+ /**
9
+ * Standard metadata for all Kustodian resources.
10
+ */
11
+ export const metadata_schema = z.object({
12
+ name: z.string().min(1),
13
+ });
14
+
15
+ export type MetadataType = z.infer<typeof metadata_schema>;
16
+
17
+ /**
18
+ * Health check configuration for waiting on resources.
19
+ */
20
+ export const health_check_schema = z.object({
21
+ kind: z.string().min(1),
22
+ name: z.string().min(1),
23
+ namespace: z.string().min(1).optional(),
24
+ });
25
+
26
+ export type HealthCheckType = z.infer<typeof health_check_schema>;
27
+
28
+ /**
29
+ * Registry configuration for version substitutions.
30
+ */
31
+ export const registry_config_schema = z.object({
32
+ /** Full image reference: registry/namespace/image or just namespace/image for Docker Hub */
33
+ image: z.string().min(1),
34
+ /** Registry type for API selection */
35
+ type: z.enum(['dockerhub', 'ghcr']).optional(),
36
+ });
37
+
38
+ export type RegistryConfigType = z.infer<typeof registry_config_schema>;
39
+
40
+ /**
41
+ * Generic substitution (backward compatible, default type).
42
+ */
43
+ export const generic_substitution_schema = z.object({
44
+ type: z.literal('generic').optional(),
45
+ name: z.string().min(1),
46
+ default: z.string().optional(),
47
+ secret: z.string().optional(),
48
+ });
49
+
50
+ export type GenericSubstitutionType = z.infer<typeof generic_substitution_schema>;
51
+
52
+ /**
53
+ * Version substitution for tracking container image versions.
54
+ */
55
+ export const version_substitution_schema = z.object({
56
+ type: z.literal('version'),
57
+ name: z.string().min(1),
58
+ default: z.string().optional(),
59
+ /** Semver constraint: ^1.0.0, ~2.3.0, >=1.0.0 <2.0.0 */
60
+ constraint: z.string().optional(),
61
+ /** Registry configuration for fetching available versions */
62
+ registry: registry_config_schema,
63
+ /** Regex pattern for filtering valid tags (default: semver-like) */
64
+ tag_pattern: z.string().optional(),
65
+ /** Exclude pre-release versions (default: true) */
66
+ exclude_prerelease: z.boolean().optional(),
67
+ });
68
+
69
+ export type VersionSubstitutionType = z.infer<typeof version_substitution_schema>;
70
+
71
+ /**
72
+ * Namespace substitution with Kubernetes naming validation.
73
+ */
74
+ export const namespace_substitution_schema = z.object({
75
+ type: z.literal('namespace'),
76
+ name: z.string().min(1),
77
+ default: z.string().optional(),
78
+ });
79
+
80
+ export type NamespaceSubstitutionType = z.infer<typeof namespace_substitution_schema>;
81
+
82
+ /**
83
+ * 1Password substitution for fetching secrets from 1Password vaults.
84
+ * Uses the op:// secret reference format.
85
+ */
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
+ });
94
+
95
+ export type OnePasswordSubstitutionType = z.infer<typeof onepassword_substitution_schema>;
96
+
97
+ /**
98
+ * Doppler substitution for fetching secrets from Doppler projects.
99
+ */
100
+ export const doppler_substitution_schema = z.object({
101
+ type: z.literal('doppler'),
102
+ 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),
107
+ /** Secret key name in Doppler */
108
+ secret: z.string().min(1),
109
+ /** Optional default value if secret cannot be fetched */
110
+ default: z.string().optional(),
111
+ });
112
+
113
+ export type DopplerSubstitutionType = z.infer<typeof doppler_substitution_schema>;
114
+
115
+ /**
116
+ * Union of all substitution types.
117
+ * Supports backward compatibility: substitutions without 'type' are treated as generic.
118
+ */
119
+ export const substitution_schema = z.union([
120
+ version_substitution_schema,
121
+ namespace_substitution_schema,
122
+ onepassword_substitution_schema,
123
+ doppler_substitution_schema,
124
+ generic_substitution_schema, // Must be last due to optional 'type' field
125
+ ]);
126
+
127
+ export type SubstitutionType = z.infer<typeof substitution_schema>;
128
+
129
+ /**
130
+ * Type guard for version substitutions.
131
+ */
132
+ export function is_version_substitution(sub: SubstitutionType): sub is VersionSubstitutionType {
133
+ return 'type' in sub && sub.type === 'version';
134
+ }
135
+
136
+ /**
137
+ * Type guard for namespace substitutions.
138
+ */
139
+ export function is_namespace_substitution(sub: SubstitutionType): sub is NamespaceSubstitutionType {
140
+ return 'type' in sub && sub.type === 'namespace';
141
+ }
142
+
143
+ /**
144
+ * Type guard for generic substitutions.
145
+ */
146
+ export function is_generic_substitution(sub: SubstitutionType): sub is GenericSubstitutionType {
147
+ return !('type' in sub) || sub.type === 'generic' || sub.type === undefined;
148
+ }
149
+
150
+ /**
151
+ * Type guard for 1Password substitutions.
152
+ */
153
+ export function is_onepassword_substitution(
154
+ sub: SubstitutionType,
155
+ ): sub is OnePasswordSubstitutionType {
156
+ return 'type' in sub && sub.type === '1password';
157
+ }
158
+
159
+ /**
160
+ * Type guard for Doppler substitutions.
161
+ */
162
+ export function is_doppler_substitution(sub: SubstitutionType): sub is DopplerSubstitutionType {
163
+ return 'type' in sub && sub.type === 'doppler';
164
+ }
165
+
166
+ /**
167
+ * Namespace configuration with fallback behavior.
168
+ */
169
+ export const namespace_config_schema = z.object({
170
+ default: z.string().min(1),
171
+ create: z.boolean().optional().default(true),
172
+ });
173
+
174
+ export type NamespaceConfigType = z.infer<typeof namespace_config_schema>;
175
+
176
+ /**
177
+ * Key-value pairs for substitution values.
178
+ */
179
+ export const values_schema = z.record(z.string(), z.string());
180
+
181
+ export type ValuesType = z.infer<typeof values_schema>;
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './template.js';
2
+ export * from './cluster.js';
3
+ export * from './common.js';
4
+ export * from './node-list.js';
5
+ export * from './profile.js';
@@ -0,0 +1,135 @@
1
+ import { z } from 'zod';
2
+
3
+ import { api_version_schema } from './common.js';
4
+
5
+ /**
6
+ * SSH configuration schema.
7
+ */
8
+ export const ssh_config_schema = z.object({
9
+ user: z.string().optional(),
10
+ key_path: z.string().optional(),
11
+ known_hosts_path: z.string().optional(),
12
+ port: z.number().int().positive().optional(),
13
+ });
14
+
15
+ export type SshConfigSchemaType = z.infer<typeof ssh_config_schema>;
16
+
17
+ /**
18
+ * Kubernetes taint effect.
19
+ */
20
+ export const taint_effect_schema = z.enum(['NoSchedule', 'PreferNoSchedule', 'NoExecute']);
21
+
22
+ export type TaintEffectType = z.infer<typeof taint_effect_schema>;
23
+
24
+ /**
25
+ * Kubernetes taint schema.
26
+ */
27
+ export const taint_schema = z.object({
28
+ key: z.string().min(1),
29
+ value: z.string().optional(),
30
+ effect: taint_effect_schema,
31
+ });
32
+
33
+ export type TaintSchemaType = z.infer<typeof taint_schema>;
34
+
35
+ /**
36
+ * Node role in the cluster.
37
+ */
38
+ export const node_role_schema = z.enum(['controller', 'worker', 'controller+worker']);
39
+
40
+ export type NodeRoleType = z.infer<typeof node_role_schema>;
41
+
42
+ /**
43
+ * Single node definition schema (for inline use in NodeList).
44
+ */
45
+ export const node_schema = z.object({
46
+ name: z.string().min(1),
47
+ role: node_role_schema,
48
+ address: z.string().min(1),
49
+ profile: z.string().min(1).optional(),
50
+ ssh: ssh_config_schema.optional(),
51
+ labels: z.record(z.union([z.string(), z.boolean(), z.number()])).optional(),
52
+ taints: z.array(taint_schema).optional(),
53
+ annotations: z.record(z.string()).optional(),
54
+ });
55
+
56
+ export type NodeSchemaType = z.infer<typeof node_schema>;
57
+
58
+ /**
59
+ * Node metadata schema (for standalone Node resources).
60
+ */
61
+ export const node_metadata_schema = z.object({
62
+ name: z.string().min(1),
63
+ cluster: z.string().min(1),
64
+ });
65
+
66
+ export type NodeMetadataType = z.infer<typeof node_metadata_schema>;
67
+
68
+ /**
69
+ * Node spec schema (for standalone Node resources).
70
+ */
71
+ export const node_spec_schema = z.object({
72
+ role: node_role_schema,
73
+ address: z.string().min(1),
74
+ profile: z.string().min(1).optional(),
75
+ ssh: ssh_config_schema.optional(),
76
+ labels: z.record(z.union([z.string(), z.boolean(), z.number()])).optional(),
77
+ taints: z.array(taint_schema).optional(),
78
+ annotations: z.record(z.string()).optional(),
79
+ });
80
+
81
+ export type NodeSpecType = z.infer<typeof node_spec_schema>;
82
+
83
+ /**
84
+ * Standalone Node resource definition.
85
+ * Used for individual node files at clusters/<cluster>/nodes/<node>.yml
86
+ */
87
+ export const node_resource_schema = z.object({
88
+ apiVersion: api_version_schema,
89
+ kind: z.literal('Node'),
90
+ metadata: node_metadata_schema,
91
+ spec: node_spec_schema,
92
+ });
93
+
94
+ export type NodeResourceType = z.infer<typeof node_resource_schema>;
95
+
96
+ /**
97
+ * Validates a Node resource and returns the result.
98
+ */
99
+ export function validate_node_resource(
100
+ data: unknown,
101
+ ): z.SafeParseReturnType<unknown, NodeResourceType> {
102
+ return node_resource_schema.safeParse(data);
103
+ }
104
+
105
+ /**
106
+ * Converts a Node resource to a NodeType for internal use.
107
+ */
108
+ export function node_resource_to_node(resource: NodeResourceType): NodeSchemaType {
109
+ const node: NodeSchemaType = {
110
+ name: resource.metadata.name,
111
+ role: resource.spec.role,
112
+ address: resource.spec.address,
113
+ };
114
+
115
+ if (resource.spec.profile !== undefined) {
116
+ node.profile = resource.spec.profile;
117
+ }
118
+ if (resource.spec.ssh !== undefined) {
119
+ node.ssh = resource.spec.ssh;
120
+ }
121
+ if (resource.spec.labels !== undefined) {
122
+ node.labels = resource.spec.labels;
123
+ }
124
+ if (resource.spec.taints !== undefined) {
125
+ node.taints = resource.spec.taints;
126
+ }
127
+ if (resource.spec.annotations !== undefined) {
128
+ node.annotations = resource.spec.annotations;
129
+ }
130
+
131
+ return node;
132
+ }
133
+
134
+ // NodeList is no longer a schema kind - it's just an internal construct
135
+ // Nodes are defined as individual Node resources and aggregated in code
package/src/profile.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+
3
+ import { api_version_schema } from './common.js';
4
+ import { taint_schema } from './node-list.js';
5
+
6
+ /**
7
+ * Node profile specification schema.
8
+ * Defines reusable configuration for labels, taints, and annotations.
9
+ */
10
+ export const node_profile_spec_schema = z.object({
11
+ name: z.string().min(1).optional(),
12
+ description: z.string().optional(),
13
+ labels: z.record(z.union([z.string(), z.boolean(), z.number()])).optional(),
14
+ taints: z.array(taint_schema).optional(),
15
+ annotations: z.record(z.string()).optional(),
16
+ });
17
+
18
+ export type NodeProfileSpecType = z.infer<typeof node_profile_spec_schema>;
19
+
20
+ /**
21
+ * Node profile metadata schema.
22
+ */
23
+ export const node_profile_metadata_schema = z.object({
24
+ name: z.string().min(1),
25
+ });
26
+
27
+ export type NodeProfileMetadataType = z.infer<typeof node_profile_metadata_schema>;
28
+
29
+ /**
30
+ * Standalone NodeProfile resource definition.
31
+ * Used for profile files at profiles/<profile-name>.yaml
32
+ */
33
+ export const node_profile_resource_schema = z.object({
34
+ apiVersion: api_version_schema,
35
+ kind: z.literal('NodeProfile'),
36
+ metadata: node_profile_metadata_schema,
37
+ spec: node_profile_spec_schema,
38
+ });
39
+
40
+ export type NodeProfileResourceType = z.infer<typeof node_profile_resource_schema>;
41
+
42
+ /**
43
+ * Validates a NodeProfile resource and returns the result.
44
+ */
45
+ export function validate_node_profile_resource(
46
+ data: unknown,
47
+ ): z.SafeParseReturnType<unknown, NodeProfileResourceType> {
48
+ return node_profile_resource_schema.safeParse(data);
49
+ }
50
+
51
+ /**
52
+ * Internal node profile type for use after loading.
53
+ */
54
+ export interface NodeProfileType {
55
+ name: string;
56
+ display_name?: string;
57
+ description?: string;
58
+ labels?: Record<string, string | boolean | number>;
59
+ taints?: z.infer<typeof taint_schema>[];
60
+ annotations?: Record<string, string>;
61
+ }
62
+
63
+ /**
64
+ * Converts a NodeProfile resource to a NodeProfileType for internal use.
65
+ */
66
+ export function node_profile_resource_to_profile(
67
+ resource: NodeProfileResourceType,
68
+ ): NodeProfileType {
69
+ const profile: NodeProfileType = {
70
+ name: resource.metadata.name,
71
+ };
72
+
73
+ if (resource.spec.name !== undefined) {
74
+ profile.display_name = resource.spec.name;
75
+ }
76
+ if (resource.spec.description !== undefined) {
77
+ profile.description = resource.spec.description;
78
+ }
79
+ if (resource.spec.labels !== undefined) {
80
+ profile.labels = resource.spec.labels;
81
+ }
82
+ if (resource.spec.taints !== undefined) {
83
+ profile.taints = resource.spec.taints;
84
+ }
85
+ if (resource.spec.annotations !== undefined) {
86
+ profile.annotations = resource.spec.annotations;
87
+ }
88
+
89
+ return profile;
90
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+
3
+ import {
4
+ api_version_schema,
5
+ health_check_schema,
6
+ metadata_schema,
7
+ namespace_config_schema,
8
+ substitution_schema,
9
+ } from './common.js';
10
+
11
+ /**
12
+ * A single kustomization within a template.
13
+ * Maps to a Flux Kustomization resource.
14
+ */
15
+ export const kustomization_schema = z.object({
16
+ name: z.string().min(1),
17
+ path: z.string().min(1),
18
+ namespace: namespace_config_schema.optional(),
19
+ depends_on: z.array(z.string()).optional(),
20
+ substitutions: z.array(substitution_schema).optional(),
21
+ health_checks: z.array(health_check_schema).optional(),
22
+ prune: z.boolean().optional().default(true),
23
+ wait: z.boolean().optional().default(true),
24
+ timeout: z.string().optional(),
25
+ retry_interval: z.string().optional(),
26
+ });
27
+
28
+ export type KustomizationType = z.infer<typeof kustomization_schema>;
29
+
30
+ /**
31
+ * Template specification containing kustomizations.
32
+ */
33
+ export const template_spec_schema = z.object({
34
+ kustomizations: z.array(kustomization_schema).min(1),
35
+ });
36
+
37
+ export type TemplateSpecType = z.infer<typeof template_spec_schema>;
38
+
39
+ /**
40
+ * Complete Template resource definition.
41
+ */
42
+ export const template_schema = z.object({
43
+ apiVersion: api_version_schema,
44
+ kind: z.literal('Template'),
45
+ metadata: metadata_schema,
46
+ spec: template_spec_schema,
47
+ });
48
+
49
+ export type TemplateType = z.infer<typeof template_schema>;
50
+
51
+ /**
52
+ * Validates a template object and returns the result.
53
+ */
54
+ export function validate_template(data: unknown): z.SafeParseReturnType<unknown, TemplateType> {
55
+ return template_schema.safeParse(data);
56
+ }