kube_schema 1.3.2 → 1.3.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d74795f238283bb03bba7c2a82459475512cf3922704870aa2256f6235410d66
4
- data.tar.gz: 19d329b7abbad3dbf5d37daa8202a457ec14c26f23a078bafaf1e1f10c67abe1
3
+ metadata.gz: 193f973be41e7f42709ecf8d2407d47d7200ab399a441563b34342149f759956
4
+ data.tar.gz: 3a931d60cf56e1d03ed9a25eee098978a1e6f647113dc22f05f1858d0d03e045
5
5
  SHA512:
6
- metadata.gz: f11cfd8ce0f92d2c7b824eba56559860a95eca65e2c13ffbcd8177f296b26fabcf395fb66c16d8d4533fbb188e81698f4560d8f3a5f08d2536b2a8d36557eff7
7
- data.tar.gz: 7af51b827644818da72e031e858f47697678dc8d97929c7d2e5bfcd964e2202ede1507f0f20e63ba64dec6a2bebdb0e2128a99a0085fa3562ce3fac1a8d241d0
6
+ metadata.gz: d789cd2fe143a465f645f73fb7d6ecc0882ace1bbaadca2513b26053efff52bd1c162aff2fe43ee25e0bd600024366db92a221b855562d3eef4b44025f691daf
7
+ data.tar.gz: 94642b199d333cb7da54cc324c0ee29641431bcc4884e38b7e24e6d47eb97f886ece7f680f3923c336c62a55ff171d247d8578abde94e10cee0c5eda8a8759a6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kube_schema (1.3.1)
4
+ kube_schema (1.3.3)
5
5
  json_schemer (~> 2.5.0)
6
6
  rubyshell (~> 1.5.0)
7
7
 
@@ -101,6 +101,14 @@ module Kube
101
101
  schema["definitions"].merge!(extra)
102
102
  end
103
103
 
104
+ # Kubernetes OpenAPI v2 defines IntOrString as "type": "string"
105
+ # with "format": "int-or-string". This is a limitation of
106
+ # OpenAPI v2 which cannot express union types. Patch the
107
+ # definition so JSONSchemer accepts both integers and strings.
108
+ if (int_or_str = schema.dig("definitions", "io.k8s.apimachinery.pkg.util.intstr.IntOrString"))
109
+ int_or_str["type"] = ["string", "integer"]
110
+ end
111
+
104
112
  JSONSchemer.schema(schema)
105
113
  end
106
114
  end
@@ -184,6 +192,7 @@ module Kube
184
192
  Class.new(::Kube::Schema::Resource) do
185
193
  @schema = schema_instance
186
194
  @defaults = defaults
195
+ @schema_properties = @schema.value["properties"].keys.map(&:to_sym)
187
196
 
188
197
  def self.schema
189
198
  @schema || superclass.schema
@@ -192,6 +201,16 @@ module Kube
192
201
  def self.defaults
193
202
  @defaults || superclass.defaults
194
203
  end
204
+
205
+ def self.schema_properties
206
+ @schema_properties
207
+ end
208
+
209
+ schema_instance.value["properties"].keys.then do |properties|
210
+ properties.each do |prop|
211
+ define_method(prop.to_sym) { @data[prop.to_sym] }
212
+ end
213
+ end
195
214
  end
196
215
  end
197
216
 
@@ -90,8 +90,32 @@ module Kube
90
90
  # File I/O
91
91
  # -------------------------------------------------------------------
92
92
 
93
+ # Parse a YAML string containing one or more Kubernetes resource
94
+ # documents and return a Manifest populated with typed Resource objects.
95
+ #
96
+ # Each document's "kind" is resolved via Kube::Schema.parse to
97
+ # produce the correct Resource subclass (e.g. Deployment, Service).
98
+ # Documents without a recognized "kind" fall back to a bare Resource.
99
+ #
100
+ # yaml = `helm template my-release bitnami/nginx`
101
+ # manifest = Kube::Schema::Manifest.parse(yaml)
102
+ # manifest.first.class #=> Kube::Schema::Resource (Deployment subclass)
103
+ #
104
+ # @param yaml_string [String] multi-document YAML
105
+ # @return [Manifest]
106
+ def self.parse(yaml_string)
107
+ docs = if YAML.respond_to?(:safe_load_stream)
108
+ YAML.safe_load_stream(yaml_string, permitted_classes: [Symbol])
109
+ else
110
+ YAML.load_stream(yaml_string)
111
+ end
112
+
113
+ resources = docs.compact.map { |doc| parse_doc(doc) }
114
+ new(*resources)
115
+ end
116
+
93
117
  # Read a YAML file containing one or more Kubernetes resource documents
94
- # and return a Manifest populated with Resource objects.
118
+ # and return a Manifest populated with typed Resource objects.
95
119
  #
96
120
  # manifest = Kube::Schema::Manifest.open("deploy.yaml")
97
121
  # manifest.count #=> 3
@@ -108,7 +132,7 @@ module Kube
108
132
  YAML.load_stream(contents)
109
133
  end
110
134
 
111
- resources = docs.compact.map { |doc| Resource.new(doc) }
135
+ resources = docs.compact.map { |doc| parse_doc(doc) }
112
136
  new(*resources, filename: path)
113
137
  end
114
138
 
@@ -127,6 +151,16 @@ module Kube
127
151
  path
128
152
  end
129
153
 
154
+ # Parse a single YAML document hash into a typed Resource.
155
+ #
156
+ # @param doc [Hash] a parsed YAML document
157
+ # @return [Resource]
158
+ # @raise [RuntimeError] if the kind is not recognized
159
+ def self.parse_doc(doc)
160
+ Kube::Schema.parse(doc)
161
+ end
162
+ private_class_method :parse_doc
163
+
130
164
  private
131
165
 
132
166
  # Deep-stringify keys for clean YAML output.
@@ -11,12 +11,16 @@ module Kube
11
11
  # Therefore, they are ONLY ever set from the self.defaults
12
12
  # property.
13
13
  deep_symbolize_keys(hash).then do |symbolized|
14
- config = defaults.merge({
15
- metadata: symbolized.delete(:metadata) || {},
16
- spec: symbolized.delete(:spec) || {},
17
- })
18
-
19
- @data = config
14
+ @data = defaults
15
+
16
+ # This is extracting "top-level" properties from the input hash
17
+ # such as [apiVersion, spec, metadata, roleRef, ...]
18
+ # We then ignore the rest of the attributes by design.
19
+ self.class.schema_properties.each do |property|
20
+ if symbolized.key?(property)
21
+ @data[property] = symbolized.delete(property)
22
+ end
23
+ end
20
24
  end
21
25
  end
22
26
 
@@ -25,17 +29,20 @@ module Kube
25
29
  end
26
30
  end
27
31
 
28
- def apiVersion = @data.apiVersion
29
- def kind = @data.kind
30
- def spec = @data.spec
31
- def metadata = @data.metadata
32
-
33
32
  # Gets overridden by the factory in Kube::Schema::Instance
34
- def self.schema = nil
33
+ def self.schema
34
+ raise "Kube::Schema::Resource should NOT be instanciated directly"
35
+ end
36
+
37
+ def self.schema_properties
38
+ raise "Kube::Schema::Resource should NOT be instanciated directly"
39
+ end
35
40
 
36
41
  # Gets overridden by the factory in Kube::Schema::Instance.
37
42
  # Returns a frozen Hash like { "apiVersion" => "apps/v1", "kind" => "Deployment" }
38
- def self.defaults = nil
43
+ def self.defaults
44
+ raise "Kube::Schema::Resource should NOT be instanciated directly"
45
+ end
39
46
 
40
47
  def valid?
41
48
  if self.class.schema.nil?
@@ -69,7 +76,8 @@ module Kube
69
76
  # they are facts derived from the GVK metadata.
70
77
  def to_h
71
78
  defaults = self.class.defaults
72
- data = @data.reject { |_, v| v.is_a?(Hash) && v.empty? }
79
+ data = deep_compact(@data)
80
+ data = data.reject { |_, v| v.is_a?(Hash) && v.empty? }
73
81
 
74
82
  if defaults
75
83
  symbolized = deep_symbolize_keys(defaults)
@@ -95,6 +103,20 @@ module Kube
95
103
 
96
104
  private
97
105
 
106
+ def deep_compact(obj)
107
+ case obj
108
+ when Hash
109
+ obj.each_with_object({}) do |(k, v), result|
110
+ compacted = deep_compact(v)
111
+ result[k] = compacted unless compacted.nil?
112
+ end
113
+ when Array
114
+ obj.map { |v| deep_compact(v) }
115
+ else
116
+ obj
117
+ end
118
+ end
119
+
98
120
  def deep_stringify_keys(obj)
99
121
  case obj
100
122
  when Hash
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kube
4
4
  module Schema
5
- VERSION = "1.3.2"
5
+ VERSION = "1.3.4"
6
6
  end
7
7
  end
data/lib/kube/schema.rb CHANGED
@@ -48,11 +48,11 @@ module Kube
48
48
  # api_version: "cert-manager.io/v1"
49
49
  # )
50
50
  #
51
- # @example Register from a Hash
52
- # Kube::Schema.register("MyResource",
53
- # schema: { "type" => "object", "properties" => { ... } },
54
- # api_version: "example.com/v1"
55
- # )
51
+ # @example Register from Chart#crds
52
+ # chart.crds.each do |crd|
53
+ # s = crd.to_json_schema
54
+ # Kube::Schema.register(s[:kind], schema: s[:schema], api_version: s[:api_version])
55
+ # end
56
56
  #
57
57
  def register(kind, schema:, api_version:)
58
58
  require "json"
@@ -117,10 +117,26 @@ module Kube
117
117
  end
118
118
  end
119
119
 
120
- # Build a Resource from a hash.
121
- # Kube::Schema.parse(Kube::Schema["Deployment"].to_h) == Kube::Schema["Deployment"]
120
+ # Build a typed Resource from a raw hash.
121
+ #
122
+ # Looks up the "kind" key in the hash and resolves it to the
123
+ # correct Resource subclass via the schema registry. The hash
124
+ # may use string or symbol keys.
125
+ #
126
+ # Kube::Schema.parse("kind" => "Deployment", "apiVersion" => "apps/v1")
127
+ # Kube::Schema.parse(kind: "Pod", apiVersion: "v1", metadata: { name: "web" })
128
+ #
129
+ # @param hash [Hash] a Kubernetes resource hash with at least a "kind" key
130
+ # @return [Resource] a schema-validated Resource instance
131
+ # @raise [ArgumentError] if the hash is nil, not a Hash, or missing "kind"
122
132
  def parse(hash)
123
- raise NotImplementedError
133
+ raise ArgumentError, "Expected a Hash, got #{hash.class}" unless hash.is_a?(Hash)
134
+
135
+ kind = hash["kind"] || hash[:kind]
136
+ raise ArgumentError, "Hash must contain a \"kind\" key" if kind.nil?
137
+
138
+ resource_class = self[kind]
139
+ resource_class.new(hash)
124
140
  end
125
141
 
126
142
  # Available Kubernetes versions, read from the local schemas directory.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.2
4
+ version: 1.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -96,7 +96,6 @@ files:
96
96
  - ".github/workflows/update-schemas.yml"
97
97
  - ".gitignore"
98
98
  - ".rubocop.yml"
99
- - AGENTS.md
100
99
  - Gemfile
101
100
  - Gemfile.lock
102
101
  - README.md
data/AGENTS.md DELETED
@@ -1 +0,0 @@
1
- The "schemas" branch is FUCKING HUGE!!!!!!!!!!!! DO NOT FUCKING DOWNLOAD IT!!!!!!