Module: Kube::Schema

Defined in:
lib/kube/schema.rb,
lib/kube/schema/version.rb,
lib/kube/schema/instance.rb,
lib/kube/schema/manifest.rb,
lib/kube/schema/resource.rb,
lib/kube/schema/sub_spec.rb

Defined Under Namespace

Classes: Instance, Manifest, Resource, SubSpec

Constant Summary collapse

GEM_ROOT =
File.expand_path("../..", __dir__).freeze
SCHEMAS_DIR =
File.join(GEM_ROOT, "schemas").freeze
DEFAULT_VERSION =
"1.34"
VERSION =
"1.9.1"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.custom_schemasObject (readonly)

Custom schemas registered via Kube::Schema.register. Keys are kind strings, values are { schema:, defaults: } hashes.



33
34
35
# File 'lib/kube/schema.rb', line 33

def custom_schemas
  @custom_schemas
end

.schema_versionObject

Set a default Kubernetes version for bare lookups like Kube::Schema. When nil, the DEFAULT_VERSION is used.



29
30
31
# File 'lib/kube/schema.rb', line 29

def schema_version
  @schema_version
end

Class Method Details

.[](key) ⇒ Object

Kube::Schema => cached Instance (supports [“Deployment”] chaining) Kube::Schema => Resource via the default version



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/kube/schema.rb', line 103

def [](key)
  if key.start_with?("v") && Gem::Version.correct?(key.sub("v", ""))
    raise Kube::IncorrectVersionFormat,
      "\nDon't preface the version with a \"v\"." \
      "\nUse Kube::Schema[\"#{key.sub("v", "")}\"] instead."
  end

  if Gem::Version.correct?(key)
    if has_version?(key)
      @instances[key] ||= Instance.new(key)
    else
      raise Kube::UnknownVersionError.new(
        "\n#{key} is an unknown version..." +
        "\nAvailable: #{schema_versions.join(", ")}"
      )
    end
  else
    version = schema_version || DEFAULT_VERSION
    @instances[version] ||= Instance.new(version)
    @instances[version][key]
  end
end

.has_version?(version) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/kube/schema.rb', line 163

def has_version?(version)
  schema_versions.include?(version)
end

.latest_versionObject

The latest Kubernetes version available in the schemas directory.



159
160
161
# File 'lib/kube/schema.rb', line 159

def latest_version
  schema_versions.last
end

.parse(hash) ⇒ Resource

Build a typed Resource from a raw hash.

Looks up the “kind” key in the hash and resolves it to the correct Resource subclass via the schema registry. The hash may use string or symbol keys.

Kube::Schema.parse("kind" => "Deployment", "apiVersion" => "apps/v1")
Kube::Schema.parse(kind: "Pod", apiVersion: "v1", metadata: { name: "web" })

Parameters:

  • hash (Hash)

    a Kubernetes resource hash with at least a “kind” key

Returns:

  • (Resource)

    a schema-validated Resource instance

Raises:

  • (ArgumentError)

    if the hash is nil, not a Hash, or missing “kind”



138
139
140
141
142
143
144
145
146
# File 'lib/kube/schema.rb', line 138

def parse(hash)
  raise ArgumentError, "Expected a Hash, got #{hash.class}" unless hash.is_a?(Hash)

  kind = hash["kind"] || hash[:kind]
  raise ArgumentError, "Hash must contain a \"kind\" key" if kind.nil?

  resource_class = self[kind]
  resource_class.new(hash)
end

.register(kind, schema:, api_version:) ⇒ Object

Register a standalone JSON Schema for a custom resource kind.

This lets users add CRD schemas from any source — for example, the datreeio/CRDs-catalog, operator repos, or their own CRDs. Registered kinds take precedence over built-in definitions.

Examples:

Register from a local file

Kube::Schema.register("Certificate",
  schema: "schemas/cert-manager.io/certificate_v1.json",
  api_version: "cert-manager.io/v1"
)

Register from Chart#crds

chart.crds.each do |crd|
  s = crd.to_json_schema
  Kube::Schema.register(s[:kind], schema: s[:schema], api_version: s[:api_version])
end

Parameters:

  • kind (String)

    The Kubernetes Kind (e.g. “Certificate”)

  • schema (Hash, String, Pathname)

    JSON Schema as a Hash, a JSON string, or a file path to a .json file

  • api_version (String)

    The apiVersion (e.g. “cert-manager.io/v1”)



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/kube/schema.rb', line 58

def register(kind, schema:, api_version:)
  require "json"
  require "json_schemer"

  parsed = case schema
    when Hash
      schema
    when String, Pathname
      path = schema.to_s
      if File.exist?(path)
        JSON.parse(File.read(path))
      else
        JSON.parse(path)
      end
    else
      raise ArgumentError,
        "schema must be a Hash, a JSON string, or a file path — got #{schema.class}"
    end

  # Key by full GVK ("group/version/Kind") so a registered CRD resolves
  # on fully-qualified lookups (e.g. the api_version/kind path that
  # Resource#rebuild reconstructs), not just kind-only lookups. Keying by
  # bare kind also collided when two CRDs shared a kind across groups.
  key = api_version ? "#{api_version}/#{kind}" : kind
  @custom_schemas[key] = {
    schema: JSONSchemer.schema(parsed),
    defaults: { "apiVersion" => api_version, "kind" => kind }.freeze
  }

  # Invalidate cached resource classes on all instances so the
  # new registration takes effect immediately.
  @instances.each_value { |inst| inst.send(:clear_resource_cache!) }

  kind
end

.reset_custom_schemas!Object

Remove all custom schema registrations. Useful for test teardown or resetting state.



96
97
98
99
# File 'lib/kube/schema.rb', line 96

def reset_custom_schemas!
  @custom_schemas.clear
  @instances.each_value { |inst| inst.send(:clear_resource_cache!) }
end

.schema_versionsArray<String>

Available Kubernetes versions, read from the local schemas directory.

Returns:

  • (Array<String>)

    sorted version strings like [“1.19”, “1.20”, …]



151
152
153
154
155
156
# File 'lib/kube/schema.rb', line 151

def schema_versions
  @schema_versions ||=
    Dir.glob(File.join(SCHEMAS_DIR, "v*.json")).map do |file_path|
      File.basename(file_path, ".json").sub(/\Av/, "")
    end.sort_by { Gem::Version.new(_1) }
end