kube_schema 1.3.0 → 1.3.2
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -5
- data/bin/copy-schemas-over +1 -0
- data/examples/custom_crds.rb +117 -0
- data/examples/vcluster.rb +659 -0
- data/kube_schema.gemspec +0 -1
- data/lib/kube/monkey_patches.rb +8 -5
- data/lib/kube/schema/instance.rb +71 -22
- data/lib/kube/schema/resource.rb +7 -6
- data/lib/kube/schema/version.rb +1 -1
- data/lib/kube/schema.rb +71 -3
- data/schemas/loft-definitions.json +14010 -0
- metadata +4 -15
data/lib/kube/schema/instance.rb
CHANGED
|
@@ -39,44 +39,47 @@ module Kube
|
|
|
39
39
|
|
|
40
40
|
# Look up a resource by kind (e.g. "Deployment", "NetworkPolicy").
|
|
41
41
|
# Returns a class that inherits from Kube::Schema::Resource.
|
|
42
|
+
#
|
|
43
|
+
# Custom schemas registered via Kube::Schema.register take precedence
|
|
44
|
+
# over built-in definitions, allowing users to override or extend the
|
|
45
|
+
# schema for any kind.
|
|
42
46
|
def [](kind)
|
|
43
47
|
@resource_classes[kind] ||= begin
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Class.new(::Kube::Schema::Resource) do
|
|
55
|
-
@schema = ref_schema
|
|
56
|
-
@defaults = defaults
|
|
57
|
-
|
|
58
|
-
def self.schema
|
|
59
|
-
@schema || superclass.schema
|
|
48
|
+
# Custom schemas win over built-in definitions.
|
|
49
|
+
custom = find_custom_entry(kind)
|
|
50
|
+
if custom
|
|
51
|
+
build_resource_class(custom[:schema], custom[:defaults])
|
|
52
|
+
else
|
|
53
|
+
entry = find_gvk_entry(kind)
|
|
54
|
+
|
|
55
|
+
if entry.nil?
|
|
56
|
+
raise "No resource schema found for #{kind}!" \
|
|
57
|
+
"\nUse #list_resources to see available kinds for v#{version}."
|
|
60
58
|
end
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
end
|
|
60
|
+
ref_schema = schemer.ref("#/definitions/#{entry[:definition_key]}")
|
|
61
|
+
build_resource_class(ref_schema, entry[:defaults].freeze)
|
|
65
62
|
end
|
|
66
63
|
end
|
|
67
64
|
end
|
|
68
65
|
|
|
69
|
-
# All available resource kinds for this version
|
|
66
|
+
# All available resource kinds for this version, including any
|
|
67
|
+
# custom schemas registered via Kube::Schema.register.
|
|
70
68
|
#
|
|
71
69
|
# @return [Array<String>] sorted kind names
|
|
72
70
|
def list_resources
|
|
73
|
-
gvk_index.keys.sort
|
|
71
|
+
(gvk_index.keys + Schema.custom_schemas.keys).uniq.sort
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
private
|
|
77
75
|
|
|
78
76
|
# The JSONSchemer instance for this version's Swagger document.
|
|
79
77
|
# Cached at the class level so it's built once per version.
|
|
78
|
+
#
|
|
79
|
+
# After loading the base Swagger JSON, merges in any extra definition
|
|
80
|
+
# files found in the schemas directory (e.g. crd-definitions.json,
|
|
81
|
+
# loft-definitions.json). These files are flat JSON objects where keys
|
|
82
|
+
# are definition names and values are OpenAPI v2 schema objects.
|
|
80
83
|
def schemer
|
|
81
84
|
self.class.schemers[@version] ||= begin
|
|
82
85
|
path = File.join(SCHEMAS_DIR, "v#{version}.json")
|
|
@@ -87,7 +90,18 @@ module Kube
|
|
|
87
90
|
"\nUse `Kube::Schema.schema_versions` to get a list."
|
|
88
91
|
end
|
|
89
92
|
|
|
90
|
-
|
|
93
|
+
schema = JSON.parse(File.read(path))
|
|
94
|
+
|
|
95
|
+
# Merge extra definition files (*-definitions.json) into the
|
|
96
|
+
# base schema so CRD and aggregated-API types (e.g. loft,
|
|
97
|
+
# gateway-api) are available alongside built-in k8s types.
|
|
98
|
+
Dir.glob(File.join(SCHEMAS_DIR, "*-definitions.json")).each do |defs_path|
|
|
99
|
+
extra = JSON.parse(File.read(defs_path))
|
|
100
|
+
schema["definitions"] ||= {}
|
|
101
|
+
schema["definitions"].merge!(extra)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
JSONSchemer.schema(schema)
|
|
91
105
|
end
|
|
92
106
|
end
|
|
93
107
|
|
|
@@ -151,6 +165,41 @@ module Kube
|
|
|
151
165
|
|
|
152
166
|
nil
|
|
153
167
|
end
|
|
168
|
+
|
|
169
|
+
# Find a custom schema entry by kind (case-insensitive).
|
|
170
|
+
# Returns the { schema:, defaults: } hash or nil.
|
|
171
|
+
def find_custom_entry(kind)
|
|
172
|
+
registry = Schema.custom_schemas
|
|
173
|
+
return registry[kind] if registry.key?(kind)
|
|
174
|
+
|
|
175
|
+
registry.each do |k, v|
|
|
176
|
+
return v if k.downcase == kind.downcase
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Build a Resource subclass from a JSONSchemer instance and defaults hash.
|
|
183
|
+
def build_resource_class(schema_instance, defaults)
|
|
184
|
+
Class.new(::Kube::Schema::Resource) do
|
|
185
|
+
@schema = schema_instance
|
|
186
|
+
@defaults = defaults
|
|
187
|
+
|
|
188
|
+
def self.schema
|
|
189
|
+
@schema || superclass.schema
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.defaults
|
|
193
|
+
@defaults || superclass.defaults
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Called by Kube::Schema.register and reset_custom_schemas! to
|
|
199
|
+
# invalidate cached resource classes so new registrations take effect.
|
|
200
|
+
def clear_resource_cache!
|
|
201
|
+
@resource_classes.clear
|
|
202
|
+
end
|
|
154
203
|
end
|
|
155
204
|
end
|
|
156
205
|
end
|
data/lib/kube/schema/resource.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "black_hole_struct"
|
|
4
|
-
|
|
5
3
|
module Kube
|
|
6
4
|
module Schema
|
|
7
5
|
class Resource
|
|
@@ -9,13 +7,16 @@ module Kube
|
|
|
9
7
|
def initialize(hash = {}, &block)
|
|
10
8
|
deep_symbolize_keys(self.class.defaults.to_h).then do |defaults|
|
|
11
9
|
|
|
10
|
+
# You are NEVER allowed to change `apiVersion` or `kind`
|
|
11
|
+
# Therefore, they are ONLY ever set from the self.defaults
|
|
12
|
+
# property.
|
|
12
13
|
deep_symbolize_keys(hash).then do |symbolized|
|
|
13
14
|
config = defaults.merge({
|
|
14
|
-
metadata: symbolized.delete(:metadata),
|
|
15
|
-
spec: symbolized.delete(:spec),
|
|
15
|
+
metadata: symbolized.delete(:metadata) || {},
|
|
16
|
+
spec: symbolized.delete(:spec) || {},
|
|
16
17
|
})
|
|
17
18
|
|
|
18
|
-
@data =
|
|
19
|
+
@data = config
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
|
|
@@ -68,7 +69,7 @@ module Kube
|
|
|
68
69
|
# they are facts derived from the GVK metadata.
|
|
69
70
|
def to_h
|
|
70
71
|
defaults = self.class.defaults
|
|
71
|
-
data = @data.
|
|
72
|
+
data = @data.reject { |_, v| v.is_a?(Hash) && v.empty? }
|
|
72
73
|
|
|
73
74
|
if defaults
|
|
74
75
|
symbolized = deep_symbolize_keys(defaults)
|
data/lib/kube/schema/version.rb
CHANGED
data/lib/kube/schema.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative '
|
|
3
|
+
require_relative 'monkey_patches'
|
|
4
|
+
require_relative 'errors'
|
|
5
5
|
require_relative 'schema/version'
|
|
6
6
|
require_relative 'schema/resource'
|
|
7
7
|
require_relative 'schema/instance'
|
|
@@ -16,6 +16,7 @@ module Kube
|
|
|
16
16
|
|
|
17
17
|
@schema_version = nil
|
|
18
18
|
@instances = {}
|
|
19
|
+
@custom_schemas = {}
|
|
19
20
|
|
|
20
21
|
GEM_ROOT = File.expand_path("../..", __dir__).freeze
|
|
21
22
|
SCHEMAS_DIR = File.join(GEM_ROOT, "schemas").freeze
|
|
@@ -26,6 +27,71 @@ module Kube
|
|
|
26
27
|
# When nil, the DEFAULT_VERSION is used.
|
|
27
28
|
attr_accessor :schema_version
|
|
28
29
|
|
|
30
|
+
# Custom schemas registered via Kube::Schema.register.
|
|
31
|
+
# Keys are kind strings, values are { schema:, defaults: } hashes.
|
|
32
|
+
attr_reader :custom_schemas
|
|
33
|
+
|
|
34
|
+
# Register a standalone JSON Schema for a custom resource kind.
|
|
35
|
+
#
|
|
36
|
+
# This lets users add CRD schemas from any source — for example,
|
|
37
|
+
# the datreeio/CRDs-catalog, operator repos, or their own CRDs.
|
|
38
|
+
# Registered kinds take precedence over built-in definitions.
|
|
39
|
+
#
|
|
40
|
+
# @param kind [String] The Kubernetes Kind (e.g. "Certificate")
|
|
41
|
+
# @param schema [Hash, String, Pathname] JSON Schema as a Hash,
|
|
42
|
+
# a JSON string, or a file path to a .json file
|
|
43
|
+
# @param api_version [String] The apiVersion (e.g. "cert-manager.io/v1")
|
|
44
|
+
#
|
|
45
|
+
# @example Register from a local file
|
|
46
|
+
# Kube::Schema.register("Certificate",
|
|
47
|
+
# schema: "schemas/cert-manager.io/certificate_v1.json",
|
|
48
|
+
# api_version: "cert-manager.io/v1"
|
|
49
|
+
# )
|
|
50
|
+
#
|
|
51
|
+
# @example Register from a Hash
|
|
52
|
+
# Kube::Schema.register("MyResource",
|
|
53
|
+
# schema: { "type" => "object", "properties" => { ... } },
|
|
54
|
+
# api_version: "example.com/v1"
|
|
55
|
+
# )
|
|
56
|
+
#
|
|
57
|
+
def register(kind, schema:, api_version:)
|
|
58
|
+
require "json"
|
|
59
|
+
require "json_schemer"
|
|
60
|
+
|
|
61
|
+
parsed = case schema
|
|
62
|
+
when Hash
|
|
63
|
+
schema
|
|
64
|
+
when String, Pathname
|
|
65
|
+
path = schema.to_s
|
|
66
|
+
if File.exist?(path)
|
|
67
|
+
JSON.parse(File.read(path))
|
|
68
|
+
else
|
|
69
|
+
JSON.parse(path)
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
raise ArgumentError,
|
|
73
|
+
"schema must be a Hash, a JSON string, or a file path — got #{schema.class}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@custom_schemas[kind] = {
|
|
77
|
+
schema: JSONSchemer.schema(parsed),
|
|
78
|
+
defaults: { "apiVersion" => api_version, "kind" => kind }.freeze
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Invalidate cached resource classes on all instances so the
|
|
82
|
+
# new registration takes effect immediately.
|
|
83
|
+
@instances.each_value { |inst| inst.send(:clear_resource_cache!) }
|
|
84
|
+
|
|
85
|
+
kind
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Remove all custom schema registrations.
|
|
89
|
+
# Useful for test teardown or resetting state.
|
|
90
|
+
def reset_custom_schemas!
|
|
91
|
+
@custom_schemas.clear
|
|
92
|
+
@instances.each_value { |inst| inst.send(:clear_resource_cache!) }
|
|
93
|
+
end
|
|
94
|
+
|
|
29
95
|
# Kube::Schema["1.34"] => cached Instance (supports ["Deployment"] chaining)
|
|
30
96
|
# Kube::Schema["Deployment"] => Resource via the default version
|
|
31
97
|
def [](key)
|
|
@@ -45,7 +111,9 @@ module Kube
|
|
|
45
111
|
)
|
|
46
112
|
end
|
|
47
113
|
else
|
|
48
|
-
|
|
114
|
+
version = schema_version || DEFAULT_VERSION
|
|
115
|
+
@instances[version] ||= Instance.new(version)
|
|
116
|
+
@instances[version][key]
|
|
49
117
|
end
|
|
50
118
|
end
|
|
51
119
|
|