class Instance
Represents a single Kubernetes version's OpenAPI schema. Lazily loads the Swagger JSON once per version (shared across instances) and builds resource classes by kind.
instance = Kube::Schema::Instance.new("1.34") instance["Deployment"] # => Resource subclass instance["NetworkPolicy"] # => Resource subclass instance.list_resources # => sorted array of kind strings
Nested
Definitions
def [](kind)
Look up a resource by kind (e.g. "Deployment", "NetworkPolicy"). Returns a class that inherits from Kube::Schema::Resource.
Custom schemas registered via Kube::Schema.register take precedence over built-in definitions, allowing users to override or extend the schema for any kind.
Implementation
def [](kind)
@resource_classes[kind] ||= begin
# Custom schemas win over built-in definitions.
custom = find_custom_entry(kind)
if custom
build_resource_class(custom[:schema], custom[:defaults])
else
entry = find_gvk_entry(kind)
if entry.nil?
raise "No resource schema found for #{kind}!" \
"\nUse #list_resources to see available kinds for v#{version}."
end
ref_schema = schemer.ref("#/definitions/#{entry[:definition_key]}")
build_resource_class(ref_schema, entry[:defaults].freeze)
end
end
end
def list_resources
All available resource kinds for this version, including any custom schemas registered via Kube::Schema.register.
Implementation
def list_resources
(gvk_index.keys + Schema.custom_schemas.keys).uniq.sort
end
def sub_spec(name)
Look up a sub-spec definition by short name (e.g. "Container", "ContainerPort", "Volume", "Probe"). Returns a class that inherits from Kube::Schema::SubSpec.
Also accepts a full definition key like "io.k8s.api.core.v1.Container" for disambiguation.
instance.sub_spec("Container") # => SubSpec subclass instance.sub_spec("ContainerPort") # => SubSpec subclass
Implementation
def sub_spec(name)
@sub_spec_classes[name] ||= begin
definition_key = find_definition_key(name)
if definition_key.nil?
raise "No definition found for #{name}!" \
"\nUse #list_definitions to see available definitions for v#{version}."
end
ref_schema = schemer.ref("#/definitions/#{definition_key}")
build_sub_spec_class(ref_schema, name)
end
end
def list_definitions
All available definition short names for this version.
Implementation
def list_definitions
schemer.value.fetch("definitions", {}).keys
.map { |k| k.split(".").last }
.uniq.sort
end
def schemer
The JSONSchemer instance for this version's Swagger document. Cached at the class level so it's built once per version.
After loading the base Swagger JSON, merges in any extra definition files found in the schemas directory (e.g. crd-definitions.json, loft-definitions.json). These files are flat JSON objects where keys are definition names and values are OpenAPI v2 schema objects.
Implementation
def schemer
self.class.schemers[@version] ||= begin
path = File.join(SCHEMAS_DIR, "v#{version}.json")
unless File.exist?(path)
raise UnknownVersionError,
"\nNo schema file found at #{path}." \
"\nUse `Kube::Schema.schema_versions` to get a list."
end
schema = JSON.parse(File.read(path))
# Merge extra definition files (*-definitions.json) into the
# base schema so CRD and aggregated-API types (e.g. loft,
# gateway-api) are available alongside built-in k8s types.
Dir.glob(File.join(SCHEMAS_DIR, "*-definitions.json")).each do |defs_path|
extra = JSON.parse(File.read(defs_path))
schema["definitions"] ||= {}
schema["definitions"].merge!(extra)
end
# Kubernetes OpenAPI v2 defines IntOrString as "type": "string"
# with "format": "int-or-string". This is a limitation of
# OpenAPI v2 which cannot express union types. Patch the
# definition so JSONSchemer accepts both integers and strings.
if (int_or_str = schema.dig("definitions", "io.k8s.apimachinery.pkg.util.intstr.IntOrString"))
int_or_str["type"] = ["string", "integer"]
end
JSONSchemer.schema(schema)
end
end
def gvk_index
Builds a map from kind to full GVK entry:
{
"Deployment" => {
definition_key: "io.k8s.api.apps.v1.Deployment",
group: "apps",
version: "v1",
kind: "Deployment",
defaults: "apiVersion" => "apps/v1", "kind" => "Deployment"
},
"Pod" => {
definition_key: "io.k8s.api.core.v1.Pod",
group: "",
version: "v1",
kind: "Pod",
defaults: "apiVersion" => "v1", "kind" => "Pod"
},
...
}
Implementation
def gvk_index
@gvk_index ||= begin
index = {}
schemer.value.fetch("definitions", {}).each do |key, definition|
gvks = definition["x-kubernetes-group-version-kind"]
next unless gvks
gvks.each do |gvk|
group = gvk["group"].to_s
version = gvk["version"]
kind = gvk["kind"]
api_version = group.empty? ? version : "#{group}/#{version}"
index[kind] = {
definition_key: key,
group: group,
version: version,
kind: kind,
defaults: {
"apiVersion" => api_version,
"kind" => kind
}
}
end
end
index
end
end
def find_gvk_entry(kind)
Find a GVK entry by kind name (case-insensitive). Returns the full entry hash or nil.
Implementation
def find_gvk_entry(kind)
return gvk_index[kind] if gvk_index.key?(kind)
gvk_index.each do |k, v|
return v if k.downcase == kind.downcase
end
nil
end
def find_custom_entry(kind)
Find a custom schema entry by kind (case-insensitive).
Returns the schema:, defaults: hash or nil.
Implementation
def find_custom_entry(kind)
registry = Schema.custom_schemas
return registry[kind] if registry.key?(kind)
registry.each do |k, v|
return v if k.downcase == kind.downcase
end
nil
end
def build_resource_class(schema_instance, defaults)
Build a Resource subclass from a JSONSchemer instance and defaults hash.
Implementation
def build_resource_class(schema_instance, defaults)
Class.new(::Kube::Schema::Resource) do
@schema = schema_instance
@defaults = defaults
@schema_properties = @schema.value["properties"].keys.map(&:to_sym)
def self.schema
@schema || superclass.schema
end
def self.defaults
@defaults || superclass.defaults
end
def self.schema_properties
@schema_properties || superclass.schema_properties
end
schema_instance.value["properties"].keys.then do |properties|
properties.each do |prop|
define_method(prop.to_sym) { @data[prop.to_sym] }
end
end
end
end
def find_definition_key(name)
Resolve a short name like "Container" to its full definition key "io.k8s.api.core.v1.Container".
Strategy:
- Exact match on full key (for power users)
- Short-name match on last segment
- Prefer stable API versions (v1 > v1beta1 > v1alpha1)
Implementation
def find_definition_key(name)
definitions = schemer.value.fetch("definitions", {})
# Exact full-key match
return name if definitions.key?(name)
# Short-name matches (last segment after final ".")
candidates = definitions.keys.select { |k| k.split(".").last == name }
return nil if candidates.empty?
return candidates.first if candidates.size == 1
# Disambiguation: prefer stable versions, then beta, then alpha
candidates.min_by { |k|
version_segment = k.split(".")[-2].to_s
case version_segment
when /\Av\d+\z/ then [0, version_segment]
when /beta/ then [1, version_segment]
when /alpha/ then [2, version_segment]
else [3, version_segment]
end
}
end
def build_sub_spec_class(schema_instance, definition_name)
Build a SubSpec subclass from a JSONSchemer instance and a human-readable definition name (for error messages).
Implementation
def build_sub_spec_class(schema_instance, definition_name)
Class.new(::Kube::Schema::SubSpec) do
@schema = schema_instance
@definition_name = definition_name
@schema_properties = @schema.value.fetch("properties", {}).keys.map(&:to_sym)
def self.schema
@schema || superclass.schema
end
def self.definition_name
@definition_name || superclass.definition_name
end
def self.schema_properties
@schema_properties || superclass.schema_properties
end
schema_instance.value.fetch("properties", {}).keys.each do |prop|
define_method(prop.to_sym) { @data[prop.to_sym] }
end
end
end
def clear_resource_cache!
Called by Kube::Schema.register and reset_custom_schemas! to invalidate cached resource classes so new registrations take effect.
Implementation
def clear_resource_cache!
@resource_classes.clear
end