kube_schema 1.4.8 → 1.5.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/bin/create-cdi-x-values +64 -0
- data/lib/kube/monkey_patches.rb +0 -1
- data/lib/kube/schema/instance.rb +142 -21
- data/lib/kube/schema/resource.rb +0 -2
- data/lib/kube/schema/version.rb +1 -1
- data/lib/kube/schema.rb +0 -47
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3fe7b4efafcce28b1cead68ac163369fa93a88bf8fb20c1b4c93f184df24383c
|
|
4
|
+
data.tar.gz: c98ac341e688a2d93a58e470ebc307542628a2682ddc6821756ca13c75221aeb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8123faed36719b2de640ba907fa86a229eb20fa3f97b38bf72dd030dfce82c2e83673d3f61c7c1c61f51589b418baabbc3619fb61b37c8938ce4fb67d7317ac6
|
|
7
|
+
data.tar.gz: 07ea4b9cb8cfc4f9fd2b5b8721b884df1ca23cd2a586e7765e4e8a7fef0373ad9074f2f53d5987cf6deb2d5e144a0f5e35bb1f5e66078a90d4a4d738c79251b4
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# Rewrites schemas/cdi-definitions.json in-place to:
|
|
4
|
+
# 1. Add x-kubernetes-group-version-kind annotations
|
|
5
|
+
# 2. Fix $ref pointers from bare v1.* to io.k8s.* prefix
|
|
6
|
+
#
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
DEFS_FILE="schemas/cdi-definitions.json"
|
|
10
|
+
|
|
11
|
+
echo "Rewriting $DEFS_FILE..."
|
|
12
|
+
|
|
13
|
+
jq '
|
|
14
|
+
# GVK map: definition key -> [{ group, version, kind }]
|
|
15
|
+
{
|
|
16
|
+
"v1beta1.CDI": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "CDI" }],
|
|
17
|
+
"v1beta1.CDIConfig": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "CDIConfig" }],
|
|
18
|
+
"v1beta1.DataImportCron": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "DataImportCron" }],
|
|
19
|
+
"v1beta1.DataSource": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "DataSource" }],
|
|
20
|
+
"v1beta1.DataVolume": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "DataVolume" }],
|
|
21
|
+
"v1beta1.ObjectTransfer": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "ObjectTransfer" }],
|
|
22
|
+
"v1beta1.StorageProfile": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "StorageProfile" }],
|
|
23
|
+
"v1beta1.VolumeCloneSource": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "VolumeCloneSource" }],
|
|
24
|
+
"v1beta1.VolumeImportSource": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "VolumeImportSource" }],
|
|
25
|
+
"v1beta1.VolumeUploadSource": [{ group: "cdi.kubevirt.io", version: "v1beta1", kind: "VolumeUploadSource" }],
|
|
26
|
+
"v1beta1.UploadTokenRequest": [{ group: "upload.cdi.kubevirt.io", version: "v1beta1", kind: "UploadTokenRequest" }]
|
|
27
|
+
} as $gvk_map |
|
|
28
|
+
|
|
29
|
+
# Explicit $ref rewrite map: bare v1.* -> io.k8s.* full paths
|
|
30
|
+
{
|
|
31
|
+
"#/definitions/v1.Affinity": "#/definitions/io.k8s.api.core.v1.Affinity",
|
|
32
|
+
"#/definitions/v1.Condition": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Condition",
|
|
33
|
+
"#/definitions/v1.Duration": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Duration",
|
|
34
|
+
"#/definitions/v1.LabelSelector": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
|
|
35
|
+
"#/definitions/v1.ListMeta": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta",
|
|
36
|
+
"#/definitions/v1.LocalObjectReference": "#/definitions/io.k8s.api.core.v1.LocalObjectReference",
|
|
37
|
+
"#/definitions/v1.ObjectMeta": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
|
|
38
|
+
"#/definitions/v1.PersistentVolumeClaimSpec": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimSpec",
|
|
39
|
+
"#/definitions/v1.ResourceRequirements": "#/definitions/io.k8s.api.core.v1.ResourceRequirements",
|
|
40
|
+
"#/definitions/v1.Time": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Time",
|
|
41
|
+
"#/definitions/v1.Toleration": "#/definitions/io.k8s.api.core.v1.Toleration",
|
|
42
|
+
"#/definitions/v1.TypedLocalObjectReference": "#/definitions/io.k8s.api.core.v1.TypedLocalObjectReference",
|
|
43
|
+
"#/definitions/v1.TypedObjectReference": "#/definitions/io.k8s.api.core.v1.TypedObjectReference",
|
|
44
|
+
"#/definitions/v1.VolumeResourceRequirements": "#/definitions/io.k8s.api.core.v1.VolumeResourceRequirements"
|
|
45
|
+
} as $ref_map |
|
|
46
|
+
|
|
47
|
+
# Inject x-kubernetes-group-version-kind annotations
|
|
48
|
+
with_entries(
|
|
49
|
+
if $gvk_map[.key] then
|
|
50
|
+
.value["x-kubernetes-group-version-kind"] = $gvk_map[.key]
|
|
51
|
+
else . end
|
|
52
|
+
) |
|
|
53
|
+
|
|
54
|
+
# Fix $ref pointers using explicit mapping
|
|
55
|
+
walk(
|
|
56
|
+
if type == "string" and $ref_map[.] then $ref_map[.]
|
|
57
|
+
else . end
|
|
58
|
+
)
|
|
59
|
+
' "$DEFS_FILE" > "${DEFS_FILE}.tmp" && mv "${DEFS_FILE}.tmp" "$DEFS_FILE"
|
|
60
|
+
|
|
61
|
+
ANNOTATED=$(jq '[.[] | select(has("x-kubernetes-group-version-kind"))] | length' "$DEFS_FILE")
|
|
62
|
+
TOTAL=$(jq 'length' "$DEFS_FILE")
|
|
63
|
+
STALE_REFS=$(jq -r '[.. | strings | select(startswith("#/definitions/v1."))] | length' "$DEFS_FILE")
|
|
64
|
+
echo "Done. $ANNOTATED/$TOTAL definitions annotated. $STALE_REFS stale v1.* refs remaining."
|
data/lib/kube/monkey_patches.rb
CHANGED
data/lib/kube/schema/instance.rb
CHANGED
|
@@ -38,29 +38,52 @@ module Kube
|
|
|
38
38
|
@version = version
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# Look up a resource by kind
|
|
41
|
+
# Look up a resource by kind or full GVK string.
|
|
42
|
+
#
|
|
43
|
+
# Accepts:
|
|
44
|
+
# instance["Deployment"] — kind-only lookup
|
|
45
|
+
# instance["apps/v1/Deployment"] — group/version/kind
|
|
46
|
+
# instance["v1/Pod"] — version/kind (core, empty group)
|
|
47
|
+
# instance["networking.k8s.io/v1/Ingress"] — fully qualified
|
|
48
|
+
#
|
|
42
49
|
# Returns a class that inherits from Kube::Schema::Resource.
|
|
43
50
|
#
|
|
44
51
|
# Custom schemas registered via Kube::Schema.register take precedence
|
|
45
|
-
# over built-in definitions
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
# over built-in definitions (kind-only lookups only).
|
|
53
|
+
def [](input)
|
|
54
|
+
@resource_classes[input] ||= begin
|
|
55
|
+
if input.include?("/")
|
|
56
|
+
parts = input.split("/")
|
|
57
|
+
|
|
58
|
+
case parts.length
|
|
59
|
+
when 3
|
|
60
|
+
group, version, kind = parts
|
|
61
|
+
when 2
|
|
62
|
+
group = ""
|
|
63
|
+
version, kind = parts
|
|
64
|
+
else
|
|
65
|
+
raise "Invalid GVK format: #{input.inspect}." \
|
|
66
|
+
"\nExpected \"group/version/kind\" or \"version/kind\"."
|
|
67
|
+
end
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
entry = find_gvk_entry_by_full_gvk(group, version, kind)
|
|
70
|
+
else
|
|
71
|
+
# Kind-only lookup — custom schemas take precedence.
|
|
72
|
+
custom = find_custom_entry(input)
|
|
73
|
+
if custom
|
|
74
|
+
return build_resource_class(custom[:schema], custom[:defaults])
|
|
59
75
|
end
|
|
60
76
|
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
entry = find_gvk_entry(input)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if entry.nil?
|
|
81
|
+
raise "No resource schema found for #{input.inspect}!" \
|
|
82
|
+
"\nUse #list_resources to see available kinds for v#{@version}."
|
|
63
83
|
end
|
|
84
|
+
|
|
85
|
+
ref_schema = schemer.ref("#/definitions/#{entry[:definition_key]}")
|
|
86
|
+
build_resource_class(ref_schema, entry[:defaults].freeze)
|
|
64
87
|
end
|
|
65
88
|
end
|
|
66
89
|
|
|
@@ -167,7 +190,7 @@ module Kube
|
|
|
167
190
|
# }
|
|
168
191
|
def gvk_index
|
|
169
192
|
@gvk_index ||= begin
|
|
170
|
-
index = {}
|
|
193
|
+
index = Hash.new { |h, k| h[k] = [] }
|
|
171
194
|
|
|
172
195
|
schemer.value.fetch("definitions", {}).each do |key, definition|
|
|
173
196
|
gvks = definition["x-kubernetes-group-version-kind"]
|
|
@@ -179,7 +202,7 @@ module Kube
|
|
|
179
202
|
kind = gvk["kind"]
|
|
180
203
|
api_version = group.empty? ? version : "#{group}/#{version}"
|
|
181
204
|
|
|
182
|
-
index[kind]
|
|
205
|
+
index[kind] << {
|
|
183
206
|
definition_key: key,
|
|
184
207
|
group: group,
|
|
185
208
|
version: version,
|
|
@@ -197,17 +220,29 @@ module Kube
|
|
|
197
220
|
end
|
|
198
221
|
|
|
199
222
|
# Find a GVK entry by kind name (case-insensitive).
|
|
200
|
-
# Returns the
|
|
223
|
+
# Returns the first matching entry hash or nil.
|
|
201
224
|
def find_gvk_entry(kind)
|
|
202
|
-
|
|
225
|
+
entries = gvk_index[kind]
|
|
226
|
+
return entries.first if entries && !entries.empty?
|
|
203
227
|
|
|
204
228
|
gvk_index.each do |k, v|
|
|
205
|
-
return v if k.downcase == kind.downcase
|
|
229
|
+
return v.first if k.downcase == kind.downcase
|
|
206
230
|
end
|
|
207
231
|
|
|
208
232
|
nil
|
|
209
233
|
end
|
|
210
234
|
|
|
235
|
+
# Find a GVK entry by exact group, version, and kind.
|
|
236
|
+
# Returns the matching entry hash or nil.
|
|
237
|
+
def find_gvk_entry_by_full_gvk(group, version, kind)
|
|
238
|
+
entries = gvk_index[kind]
|
|
239
|
+
return nil if entries.nil? || entries.empty?
|
|
240
|
+
|
|
241
|
+
entries.find do |entry|
|
|
242
|
+
entry[:group] == group && entry[:version] == version
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
211
246
|
# Find a custom schema entry by kind (case-insensitive).
|
|
212
247
|
# Returns the { schema:, defaults: } hash or nil.
|
|
213
248
|
def find_custom_entry(kind)
|
|
@@ -391,6 +426,46 @@ if __FILE__ == $0
|
|
|
391
426
|
end
|
|
392
427
|
end
|
|
393
428
|
|
|
429
|
+
describe "#[] with full GVK string" do
|
|
430
|
+
it "resolves apps/v1/Deployment" do
|
|
431
|
+
klass = instance["apps/v1/Deployment"]
|
|
432
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
433
|
+
expect(klass.defaults).to eq({ "apiVersion" => "apps/v1", "kind" => "Deployment" })
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
it "resolves v1/Pod (core resource, empty group)" do
|
|
437
|
+
klass = instance["v1/Pod"]
|
|
438
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
439
|
+
expect(klass.defaults).to eq({ "apiVersion" => "v1", "kind" => "Pod" })
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
it "resolves networking.k8s.io/v1/Ingress" do
|
|
443
|
+
klass = instance["networking.k8s.io/v1/Ingress"]
|
|
444
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
445
|
+
expect(klass.defaults).to eq({ "apiVersion" => "networking.k8s.io/v1", "kind" => "Ingress" })
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
it "resolves kubevirt.io/v1/VirtualMachine" do
|
|
449
|
+
klass = instance["kubevirt.io/v1/VirtualMachine"]
|
|
450
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
451
|
+
expect(klass.defaults).to eq({ "apiVersion" => "kubevirt.io/v1", "kind" => "VirtualMachine" })
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
it "raises for invalid GVK format (too many slashes)" do
|
|
455
|
+
expect { instance["a/b/c/d"] }.to raise_error(RuntimeError, /Invalid GVK format/)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
it "raises for non-existent GVK" do
|
|
459
|
+
expect { instance["fake.io/v99/Blah"] }.to raise_error(RuntimeError, /No resource schema found/)
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
it "caches GVK lookups" do
|
|
463
|
+
a = instance["apps/v1/Deployment"]
|
|
464
|
+
b = instance["apps/v1/Deployment"]
|
|
465
|
+
expect(a).to be(b)
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
394
469
|
describe "#list_resources" do
|
|
395
470
|
it "returns a sorted array of kind strings" do
|
|
396
471
|
kinds = instance.list_resources
|
|
@@ -453,6 +528,52 @@ if __FILE__ == $0
|
|
|
453
528
|
end
|
|
454
529
|
end
|
|
455
530
|
|
|
531
|
+
describe "CloudnativePG schemas" do
|
|
532
|
+
it "resolves Cluster by kind" do
|
|
533
|
+
klass = instance["Cluster"]
|
|
534
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
535
|
+
expect(klass.defaults).to eq({ "apiVersion" => "postgresql.cnpg.io/v1", "kind" => "Cluster" })
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
it "resolves Cluster by full GVK string" do
|
|
539
|
+
klass = instance["postgresql.cnpg.io/v1/Cluster"]
|
|
540
|
+
expect(klass).to be < Kube::Schema::Resource
|
|
541
|
+
expect(klass.defaults).to eq({ "apiVersion" => "postgresql.cnpg.io/v1", "kind" => "Cluster" })
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it "includes Cluster in list_resources" do
|
|
545
|
+
expect(instance.list_resources).to include("Cluster")
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
it "resolves Backup" do
|
|
549
|
+
klass = instance["postgresql.cnpg.io/v1/Backup"]
|
|
550
|
+
expect(klass.defaults).to eq({ "apiVersion" => "postgresql.cnpg.io/v1", "kind" => "Backup" })
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
it "resolves ScheduledBackup" do
|
|
554
|
+
klass = instance["postgresql.cnpg.io/v1/ScheduledBackup"]
|
|
555
|
+
expect(klass.defaults).to eq({ "apiVersion" => "postgresql.cnpg.io/v1", "kind" => "ScheduledBackup" })
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
it "resolves Pooler" do
|
|
559
|
+
klass = instance["postgresql.cnpg.io/v1/Pooler"]
|
|
560
|
+
expect(klass.defaults).to eq({ "apiVersion" => "postgresql.cnpg.io/v1", "kind" => "Pooler" })
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
it "can instantiate a Cluster with the block DSL" do
|
|
564
|
+
resource = instance["postgresql.cnpg.io/v1/Cluster"].new {
|
|
565
|
+
metadata.name = "pg-cluster"
|
|
566
|
+
metadata.namespace = "databases"
|
|
567
|
+
spec.instances = 3
|
|
568
|
+
spec.storage.size = "10Gi"
|
|
569
|
+
}
|
|
570
|
+
expect(resource.to_h[:apiVersion]).to eq("postgresql.cnpg.io/v1")
|
|
571
|
+
expect(resource.to_h[:kind]).to eq("Cluster")
|
|
572
|
+
expect(resource.to_h[:metadata][:name]).to eq("pg-cluster")
|
|
573
|
+
expect(resource.to_h[:spec][:instances]).to eq(3)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
456
577
|
describe "class-level schemer cache" do
|
|
457
578
|
it "shares the schemer across instances of the same version" do
|
|
458
579
|
a = described_class.new("1.34")
|
data/lib/kube/schema/resource.rb
CHANGED
|
@@ -352,7 +352,6 @@ if __FILE__ == $0
|
|
|
352
352
|
|
|
353
353
|
expect(yaml).to include("kind: Pod")
|
|
354
354
|
expect(yaml).to include("apiVersion: v1")
|
|
355
|
-
expect(yaml).not_to include("BlackHoleStruct")
|
|
356
355
|
expect(yaml).not_to include("!ruby/object")
|
|
357
356
|
end
|
|
358
357
|
|
|
@@ -435,7 +434,6 @@ if __FILE__ == $0
|
|
|
435
434
|
yaml = deployment.to_yaml
|
|
436
435
|
|
|
437
436
|
expect(yaml).not_to include("!ruby/object")
|
|
438
|
-
expect(yaml).not_to include("BlackHoleStruct")
|
|
439
437
|
expect(yaml).not_to include("table:")
|
|
440
438
|
end
|
|
441
439
|
|
data/lib/kube/schema/version.rb
CHANGED
data/lib/kube/schema.rb
CHANGED
|
@@ -162,53 +162,6 @@ module Kube
|
|
|
162
162
|
end
|
|
163
163
|
end
|
|
164
164
|
|
|
165
|
-
# Patch BlackHoleStruct to handle arrays consistently.
|
|
166
|
-
#
|
|
167
|
-
# The upstream gem does not recurse into arrays — hashes inside arrays
|
|
168
|
-
# are not converted to BlackHoleStruct on construction, and are not
|
|
169
|
-
# converted back to plain Hash on #to_h. This causes key-type
|
|
170
|
-
# inconsistencies after a Resource round-trip (symbol keys become
|
|
171
|
-
# string keys inside arrays).
|
|
172
|
-
#
|
|
173
|
-
# These two patches fix both directions:
|
|
174
|
-
# initialize — converts hashes inside arrays to BlackHoleStruct
|
|
175
|
-
# to_h — converts BlackHoleStruct/arrays back to plain objects
|
|
176
|
-
class BlackHoleStruct
|
|
177
|
-
def initialize(hash = {})
|
|
178
|
-
raise ArgumentError, "Argument should be a Hash" unless hash.is_a?(Hash)
|
|
179
|
-
|
|
180
|
-
@table = {}
|
|
181
|
-
hash.each do |key, value|
|
|
182
|
-
@table[key.to_sym] = deep_wrap(value)
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def to_h
|
|
187
|
-
hash = {}
|
|
188
|
-
@table.each do |key, value|
|
|
189
|
-
hash[key] = deep_unwrap(value)
|
|
190
|
-
end
|
|
191
|
-
hash
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
private
|
|
195
|
-
|
|
196
|
-
def deep_wrap(value)
|
|
197
|
-
case value
|
|
198
|
-
when Hash then self.class.new(value)
|
|
199
|
-
when Array then value.map { |v| deep_wrap(v) }
|
|
200
|
-
else value
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def deep_unwrap(value)
|
|
205
|
-
case value
|
|
206
|
-
when self.class then value.to_h
|
|
207
|
-
when Array then value.map { |v| deep_unwrap(v) }
|
|
208
|
-
else value
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
165
|
|
|
213
166
|
if __FILE__ == $0
|
|
214
167
|
require "bundler/setup"
|
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.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan K
|
|
@@ -104,6 +104,7 @@ files:
|
|
|
104
104
|
- assets/validation-error.png
|
|
105
105
|
- bin/console
|
|
106
106
|
- bin/copy-schemas-over
|
|
107
|
+
- bin/create-cdi-x-values
|
|
107
108
|
- bin/create-kubevirt-x-values
|
|
108
109
|
- bin/increment-version
|
|
109
110
|
- bin/release-gem
|