kube_schemaSourceKubeSchemaInstance

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:

  1. Exact match on full key (for power users)
  2. Short-name match on last segment
  3. 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