kube_schema 1.0.0 → 1.1.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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kube_schema (0.1.1)
4
+ kube_schema (1.0.0)
5
5
  black_hole_struct (~> 0.1)
6
6
  json_schemer (~> 2.5)
7
7
  rubyshell (~> 1.5)
@@ -14,6 +14,7 @@ GEM
14
14
  ast (2.4.3)
15
15
  bigdecimal (4.0.1)
16
16
  black_hole_struct (0.1.3)
17
+ diff-lcs (1.6.2)
17
18
  hana (1.3.7)
18
19
  json (2.19.2)
19
20
  json-schema (6.2.0)
@@ -28,7 +29,6 @@ GEM
28
29
  lint_roller (1.1.0)
29
30
  mcp (0.8.0)
30
31
  json-schema (>= 4.1)
31
- minitest (5.27.0)
32
32
  parallel (1.27.0)
33
33
  parser (3.3.10.2)
34
34
  ast (~> 2.4.1)
@@ -39,6 +39,19 @@ GEM
39
39
  rainbow (3.1.1)
40
40
  rake (13.3.1)
41
41
  regexp_parser (2.11.3)
42
+ rspec (3.13.2)
43
+ rspec-core (~> 3.13.0)
44
+ rspec-expectations (~> 3.13.0)
45
+ rspec-mocks (~> 3.13.0)
46
+ rspec-core (3.13.6)
47
+ rspec-support (~> 3.13.0)
48
+ rspec-expectations (3.13.5)
49
+ diff-lcs (>= 1.2.0, < 2.0)
50
+ rspec-support (~> 3.13.0)
51
+ rspec-mocks (3.13.8)
52
+ diff-lcs (>= 1.2.0, < 2.0)
53
+ rspec-support (~> 3.13.0)
54
+ rspec-support (3.13.7)
42
55
  rubocop (1.85.1)
43
56
  json (~> 2.3)
44
57
  language_server-protocol (~> 3.17.0.2)
@@ -67,8 +80,8 @@ PLATFORMS
67
80
 
68
81
  DEPENDENCIES
69
82
  kube_schema!
70
- minitest (~> 5.0)
71
83
  rake (~> 13.0)
84
+ rspec (~> 3.13)
72
85
  rubocop (~> 1.21)
73
86
 
74
87
  BUNDLED WITH
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # KubeSchema
1
+ # Kube::Schema
2
2
 
3
3
  Ruby objects for Kubernetes OpenAPI schemas.
4
4
 
@@ -11,7 +11,7 @@ gem "kube_schema"
11
11
  ## Usage
12
12
 
13
13
  ```ruby
14
- Deployment = KubeSchema["1.33.6"]["Deployment"]
14
+ Deployment = Kube::Schema["1.33.6"]["Deployment"]
15
15
 
16
16
  app = Deployment.new do
17
17
  self.description = "My app"
@@ -24,7 +24,7 @@ app.properties # => {"apiVersion" => ..., "kind" => ..., ...}
24
24
  ```
25
25
 
26
26
  ```ruby
27
- class RailsApp < KubeSchema["1.33.6"]["Deployment"]
27
+ class RailsApp < Kube::Schema["1.33.6"]["Deployment"]
28
28
  def is_the_best?
29
29
  true
30
30
  end
data/Rakefile CHANGED
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rake/testtask"
3
+ require "rspec/core/rake_task"
4
4
 
5
- Rake::TestTask.new(:test) do |t|
6
- t.libs << "test"
7
- t.libs << "lib"
8
- t.test_files = FileList["test/**/*_test.rb"]
9
- end
5
+ RSpec::Core::RakeTask.new(:spec)
10
6
 
11
- task default: :test
7
+ task default: :spec
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "kube_schema"
5
+ require "kube/schema"
6
6
  require "irb"
7
7
 
8
8
  IRB.start(__FILE__)
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "erb"
5
- require_relative "../lib/kube_schema/version"
5
+ require_relative "../lib/kube/schema/version"
6
6
 
7
7
  USAGE = <<~TEXT
8
8
  Usage: bin/increment-version <major|minor|patch>
@@ -15,7 +15,7 @@ unless %w[major minor patch].include?(segment)
15
15
  exit 1
16
16
  end
17
17
 
18
- current = KubeSchema::VERSION
18
+ current = Kube::Schema::VERSION
19
19
  major, minor, patch = current.split(".").map(&:to_i)
20
20
 
21
21
  case segment
@@ -32,8 +32,8 @@ end
32
32
 
33
33
  version = "#{major}.#{minor}.#{patch}"
34
34
 
35
- template_path = File.expand_path("../lib/kube_schema/version.rb.erb", __dir__)
36
- output_path = File.expand_path("../lib/kube_schema/version.rb", __dir__)
35
+ template_path = File.expand_path("../lib/kube/schema/version.rb.erb", __dir__)
36
+ output_path = File.expand_path("../lib/kube/schema/version.rb", __dir__)
37
37
 
38
38
  template = ERB.new(File.read(template_path))
39
39
  result = template.result(binding)
@@ -41,4 +41,3 @@ result = template.result(binding)
41
41
  File.write(output_path, result)
42
42
 
43
43
  puts "#{current} -> #{version}"
44
-
data/bin/release-gem CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/kube_schema/version"
4
+ require_relative "../lib/kube/schema/version"
5
5
 
6
- local_version = KubeSchema::VERSION
6
+ local_version = Kube::Schema::VERSION
7
7
  gem_name = "kube_schema"
8
8
  gemspec = "kube_schema.gemspec"
9
9
 
data/bin/tag-version CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/kube_schema/version"
4
+ require_relative "../lib/kube/schema/version"
5
5
 
6
- version = KubeSchema::VERSION
6
+ version = Kube::Schema::VERSION
7
7
  tag = "v#{version}"
8
8
 
9
9
  existing = `git tag -l #{tag}`.strip
data/bin/test CHANGED
@@ -1,14 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- $LOAD_PATH.unshift File.expand_path("../test", __dir__)
5
-
6
4
  require "bundler/setup"
7
5
 
8
6
  Dir.chdir(File.expand_path("..", __dir__))
9
7
 
10
8
  if ARGV.empty?
11
- Dir.glob("test/**/*_test.rb").sort.each { |f| require_relative "../#{f}" }
9
+ exec("bundle", "exec", "rspec")
12
10
  else
13
- ARGV.each { |f| require_relative "../#{f}" }
11
+ exec("bundle", "exec", "rspec", *ARGV)
14
12
  end
data/kube_schema.gemspec CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/kube_schema/version"
3
+ require_relative "lib/kube/schema/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "kube_schema"
7
- spec.version = KubeSchema::VERSION
7
+ spec.version = Kube::Schema::VERSION
8
8
  spec.authors = ["Nathan K"]
9
9
  spec.email = ["nathankidd@hey.com"]
10
10
 
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
 
31
- spec.add_development_dependency "minitest", "~> 5.0"
31
+ spec.add_development_dependency "rspec", "~> 3.13"
32
32
  spec.add_development_dependency "rake", "~> 13.0"
33
33
  spec.add_development_dependency "rubocop", "~> 1.21"
34
34
 
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Kube
6
+ module Schema
7
+ # Represents a single Kubernetes version's OpenAPI schema.
8
+ # Lazily loads the JSON and builds an index of group/version/kind strings
9
+ # mapped back to definition keys.
10
+ #
11
+ # instance = Kube::Schema::Instance.new("v1.33.6")
12
+ # instance["Deployment"]
13
+ # instance["apps/v1/Deployment"]
14
+ # instance.list_resources #=> sorted array of "group/version/kind" strings
15
+ #
16
+ class Instance
17
+ attr_reader :version
18
+
19
+ def initialize(version)
20
+ @resource_classes = {}
21
+
22
+ is_a_version = -> (v) { Gem::Version.correct?(v) }
23
+
24
+ if is_a_version.(version)
25
+ @version = version
26
+ else
27
+ raise UnknownVersionError.new(
28
+ "\n#{version} is an unknown version..." +
29
+ "\nUse `Kube::Schema.schema_versions` to get a list."
30
+ )
31
+ end
32
+ end
33
+
34
+ # Look up a resource by full "group/version/kind" or by bare "Kind".
35
+ # Returns a class that inherits from Kube::Schema::Resource, or nil.
36
+ def [](key)
37
+ Kube::Schema::SchemaIndex.new(version).find(key.downcase).then do |path|
38
+ if path.nil?
39
+ raise "No resource schema found for #{key}!!!!!!!"
40
+ else
41
+ @resource_classes[key] ||= begin
42
+ schema_hash = JSON.parse(Kube::Schema::SchemaCache.read(path))
43
+
44
+ Class.new(::Kube::Schema::Resource) do
45
+ @schema = schema_hash
46
+
47
+ def self.schema
48
+ @schema || superclass.schema
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ostruct"
4
+ require "black_hole_struct"
5
+ require "json_schemer"
6
+
7
+ module Kube
8
+ module Schema
9
+ class Resource
10
+
11
+ # Subclasses generated by Instance#[] have a class-level .schema.
12
+ def initialize(hash = {}, &block)
13
+ hash = deep_stringify_keys(hash)
14
+
15
+ if self.class.schema
16
+ schemer = JSONSchemer.schema(self.class.schema, insert_property_defaults: true)
17
+ schemer.valid?(hash)
18
+ end
19
+
20
+ @data = BlackHoleStruct.new(hash)
21
+ @data.instance_exec(&block) if block
22
+ end
23
+
24
+ # Gets overridden by the factory in Kube::Schema::Instance
25
+ def self.schema
26
+ nil
27
+ end
28
+
29
+ def valid?
30
+ return true if self.class.schema.nil?
31
+
32
+ schemer = JSONSchemer.schema(self.class.schema)
33
+ schemer.valid?(deep_stringify_keys(to_h))
34
+ end
35
+
36
+ def to_h
37
+ @data.to_h
38
+ end
39
+
40
+ def ==(other)
41
+ other.is_a?(Resource) && to_h == other.to_h
42
+ end
43
+
44
+ private
45
+
46
+ def deep_stringify_keys(obj)
47
+ case obj
48
+ when Hash
49
+ obj.each_with_object({}) do |(k, v), result|
50
+ result[k.to_s] = deep_stringify_keys(v)
51
+ end
52
+ when Array
53
+ obj.map { |v| deep_stringify_keys(v) }
54
+ else
55
+ obj
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'net/http'
5
+ require 'uri'
6
+
7
+ module Kube
8
+ module Schema
9
+ # Lazily downloads individual schema JSON files from the schemas branch
10
+ # on GitHub and caches them locally under Gem.cache_home (XDG_CACHE_HOME).
11
+ #
12
+ # Usage:
13
+ # Kube::Schema::SchemaCache.fetch("cert-manager.io/certificate_v1")
14
+ # # => "/home/user/.cache/kube_schema/schemas/cert-manager.io/certificate_v1.json"
15
+ #
16
+ # Kube::Schema::SchemaCache.read("v1.33.6/deployment")
17
+ # # => "{...json contents...}"
18
+ #
19
+ module SchemaCache
20
+ BASE_URL = 'https://raw.githubusercontent.com/general-intelligence-systems/kube_schema/refs/heads/schemas'
21
+
22
+ class DownloadError < StandardError; end
23
+
24
+ class << self
25
+ # Root directory for cached schemas.
26
+ # Defaults to ~/.cache/kube_schema/schemas (or $XDG_CACHE_HOME/kube_schema/schemas).
27
+ def cache_dir
28
+ @cache_dir ||= File.join(Gem.cache_home, 'kube_schema', 'schemas')
29
+ end
30
+
31
+ # Override the cache directory (useful for testing).
32
+ attr_writer :cache_dir
33
+
34
+ # Returns the local file path for a schema, downloading it if not cached.
35
+ # The file_path should NOT include the .json extension.
36
+ #
37
+ # SchemaCache.fetch("cert-manager.io/certificate_v1")
38
+ # # => "/home/user/.cache/kube_schema/schemas/cert-manager.io/certificate_v1.json"
39
+ #
40
+ def fetch(file_path)
41
+ local = local_path(file_path)
42
+
43
+ if File.exist?(local)
44
+ local
45
+ else
46
+ download!(file_path, local)
47
+ end
48
+ end
49
+
50
+ # Returns the JSON content as a String, downloading if necessary.
51
+ def read(file_path)
52
+ File.read(fetch(file_path))
53
+ end
54
+
55
+ # Returns the local path where a schema would be cached (without downloading).
56
+ def local_path(file_path)
57
+ if file_path.end_with?(".json")
58
+ raise "What are you doing???? don't put .json on the end...."
59
+ else
60
+ File.join(cache_dir, "#{file_path}.json")
61
+ end
62
+ end
63
+
64
+ # Returns true if the schema is already cached locally.
65
+ def cached?(file_path)
66
+ File.exist?(local_path(file_path))
67
+ end
68
+
69
+ # Removes a single cached schema file.
70
+ def evict(file_path)
71
+ path = local_path(file_path)
72
+
73
+ if File.exist?(path)
74
+ File.delete(path)
75
+ end
76
+ end
77
+
78
+ # Removes the entire cache directory.
79
+ def clear!
80
+ FileUtils.rm_rf(cache_dir)
81
+ end
82
+
83
+ private
84
+
85
+ def download!(file_path, local)
86
+ url = "#{BASE_URL}/#{file_path}.json"
87
+ uri = URI.parse(url)
88
+
89
+ Net::HTTP.get_response(uri).then do |response|
90
+ unless response.is_a?(Net::HTTPSuccess)
91
+ raise DownloadError, "Failed to download schema: #{url} (HTTP #{response.code})"
92
+ end
93
+
94
+ FileUtils.mkdir_p(File.dirname(local))
95
+ File.write(local, response.body)
96
+ local
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,29 @@
1
+ module Kube
2
+ module Schema
3
+ class SchemaIndex
4
+ def initialize(version)
5
+ @version = version
6
+ end
7
+
8
+ def find(query)
9
+ all_paths.select { _1.include?(query.downcase) }.first
10
+ end
11
+
12
+ def all_paths
13
+ kubernetes_paths + custom_resource_paths
14
+ end
15
+
16
+ def custom_resource_paths
17
+ File.read(SCHEMA_INDEX + "/crds.txt").lines.map do |line|
18
+ line.chomp.gsub(".json", "")
19
+ end
20
+ end
21
+
22
+ def kubernetes_paths
23
+ File.read(SCHEMA_INDEX + "/v#{@version}.txt").lines.map do |line|
24
+ line.chomp.gsub(".json", "")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Schema
5
+ VERSION = "1.1.0"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Schema
5
+ VERSION = "<%= version %>"
6
+ end
7
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'schema/version'
4
+ require_relative 'schema/resource'
5
+ require_relative 'schema/instance'
6
+ require_relative 'schema/schema_cache'
7
+ require_relative 'schema/schema_index'
8
+
9
+ module Kube
10
+ module Schema
11
+ class UnknownVersionError < StandardError; end
12
+ class IncorrectVersionFormat < StandardError; end
13
+
14
+ @schema_version = nil
15
+ @instances = {}
16
+
17
+ GEM_ROOT = File.expand_path("../..", __dir__).freeze
18
+ SCHEMA_INDEX = File.join(GEM_ROOT, "data").freeze
19
+ DEFAULT_VERSION = "1.34.4" # 2025-04-16
20
+
21
+ class << self
22
+ # Set a default Kubernetes version for bare lookups like Kube::Schema["Deployment"].
23
+ # When nil, the latest version found in the schemas directory is used.
24
+ attr_accessor :schema_version
25
+
26
+ # Kube::Schema["1.33.6"] => cached Instance (supports ["Deployment"] chaining)
27
+ # Kube::Schema["Deployment"] => Resource via the default version
28
+ # Kube::Schema["apps/v1/Deployment"] => Resource via the default version
29
+ def [](key)
30
+ is_a_version = -> (key) { Gem::Version.correct?(key) }
31
+
32
+ if key.start_with?("v") && Gem::Version.correct?(key.sub("v", ""))
33
+ raise IncorrectVersionFormat,
34
+ "\nDon't preface the version with a \"v\"." \
35
+ "\nUse Kube::Schema[\"#{key.sub("v", "")}\"] instead."
36
+ end
37
+
38
+ if is_a_version.(key)
39
+ if has_version?(key)
40
+ @instances[key] ||= Instance.new(key)
41
+ else
42
+ raise UnknownVersionError.new(
43
+ "\n#{key} is an unknown version..." +
44
+ "\nUse `Kube::Schema.schema_versions` to get a list."
45
+ )
46
+ end
47
+ else
48
+ Instance.new(schema_version || DEFAULT_VERSION)[key]
49
+ end
50
+ end
51
+
52
+ # Build a Resource from a hash.
53
+ # Kube::Schema.parse(Kube::Schema["Deployment"].to_h) == Kube::Schema["Deployment"]
54
+ def parse(hash)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ def schema_versions
59
+ @schema_versions ||=
60
+ Dir.glob(SCHEMA_INDEX + "/v*.txt").map do |file_path|
61
+ file_path.split("/").last.gsub(".txt", "")[1..-1]
62
+ end.sort_by { Gem::Version.new(_1) }
63
+ end
64
+
65
+ # The latest Kubernetes version available in the schemas directory,
66
+ # determined by sorting the filenames with Gem::Version.
67
+ def latest_version
68
+ schema_versions.last
69
+ end
70
+
71
+ def has_version?(version)
72
+ schema_versions.include?(version)
73
+ end
74
+ end
75
+ end
76
+ end
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.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -10,19 +10,19 @@ cert_chain: []
10
10
  date: 1980-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: minitest
13
+ name: rspec
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '5.0'
18
+ version: '3.13'
19
19
  type: :development
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '5.0'
25
+ version: '3.13'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +109,7 @@ files:
109
109
  - ".github/workflows/update-schema-index.yml"
110
110
  - ".github/workflows/update-schemas.yml"
111
111
  - ".gitignore"
112
+ - ".rubocop.yml"
112
113
  - AGENTS.md
113
114
  - Gemfile
114
115
  - Gemfile.lock
@@ -123,13 +124,13 @@ files:
123
124
  - bin/update-schema-index
124
125
  - flake.nix
125
126
  - kube_schema.gemspec
126
- - lib/kube_schema.rb
127
- - lib/kube_schema/instance.rb
128
- - lib/kube_schema/resource.rb
129
- - lib/kube_schema/schema_cache.rb
130
- - lib/kube_schema/schema_index.rb
131
- - lib/kube_schema/version.rb
132
- - lib/kube_schema/version.rb.erb
127
+ - lib/kube/schema.rb
128
+ - lib/kube/schema/instance.rb
129
+ - lib/kube/schema/resource.rb
130
+ - lib/kube/schema/schema_cache.rb
131
+ - lib/kube/schema/schema_index.rb
132
+ - lib/kube/schema/version.rb
133
+ - lib/kube/schema/version.rb.erb
133
134
  homepage: https://github.com/n-at-han-k/kube_schema
134
135
  licenses:
135
136
  - Apache-2.0
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module KubeSchema
6
- # Represents a single Kubernetes version's OpenAPI schema.
7
- # Lazily loads the JSON and builds an index of group/version/kind strings
8
- # mapped back to definition keys.
9
- #
10
- # instance = KubeSchema::Instance.new("v1.33.6")
11
- # instance["Deployment"]
12
- # instance["apps/v1/Deployment"]
13
- # instance.list_resources #=> sorted array of "group/version/kind" strings
14
- #
15
- class Instance
16
- attr_reader :version
17
-
18
- def initialize(version)
19
- @resource_classes = {}
20
-
21
- is_a_version = -> (v) { Gem::Version.correct?(v) }
22
-
23
- if is_a_version.(version)
24
- @version = version
25
- else
26
- raise UnknownVersionError.new(
27
- "\n#{version} is an unknown version..." +
28
- "\nUse `KubeSchema.schema_versions` to get a list."
29
- )
30
- end
31
- end
32
-
33
- # Look up a resource by full "group/version/kind" or by bare "Kind".
34
- # Returns a class that inherits from KubeSchema::Resource, or nil.
35
- def [](key)
36
- KubeSchema::SchemaIndex.new(version).find(key.downcase).then do |path|
37
- if path.nil?
38
- raise "No resource schema found for #{key}!!!!!!!"
39
- else
40
- @resource_classes[key] ||= begin
41
- #schema_hash = @data["definitions"][key]
42
- Class.new(::KubeSchema::Resource) do
43
- #@schema = schema_hash
44
-
45
- #def self.schema
46
- # @schema || superclass.schema
47
- #end
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
54
- end