0.0
No release in over 3 years
Abstractions are God. Let's give praise and build on the shoulders of giants by using objects to represent schemas. It was written and to it became.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 13.0
~> 1.21
~> 3.13

Runtime

~> 2.5.0
~> 1.5.0
 Project Readme

kube_schema

Ruby objects for every Kubernetes resource. Validated against the real OpenAPI spec.

Kube::Schema["Deployment"].new {
  metadata.name = "web"
  metadata.namespace = "prod"
  spec.replicas = 3
  spec.template.spec.containers = [
    { name: "app", image: "nginx:1.27", ports: [{ containerPort: 80 }] }
  ]
}

No YAML. No hash literals. Just Ruby blocks that know their schema.

Contents

  • Install
  • Resources
  • The block DSL
  • Subclassing
  • Validation
  • Error messages
  • Manifests
    • File I/O
    • Composition
    • Enumerable
  • Schema versions
  • Related projects
  • Built with

Install

gem install kube_schema

Resources

Every Kubernetes kind is a class. Fetch it by name.

Kube::Schema["Deployment"]     # => Class < Kube::Schema::Resource
Kube::Schema["Service"]
Kube::Schema["ConfigMap"]
Kube::Schema["NetworkPolicy"]

Specific versions:

Kube::Schema["1.34"]["Deployment"]
Kube::Schema["1.31"]["Pod"]

Discovery:

Kube::Schema.schema_versions    # => ["1.19", "1.20", ..., "1.35"]
Kube::Schema.latest_version     # => "1.35"

Kube::Schema["1.34"].list_resources
# => ["Binding", "CSIDriver", "ConfigMap", "Deployment", ...]

The block DSL

Nested attributes just work. No intermediate hashes, no string keys.

deploy = Kube::Schema["Deployment"].new {
  metadata.name = "web"
  metadata.namespace = "prod"
  metadata.labels = { app: "web" }

  spec.replicas = 3
  spec.selector.matchLabels = { app: "web" }

  spec.template.metadata.labels = { app: "web" }
  spec.template.spec.containers = [
    { name: "app", image: "nginx:1.27", ports: [{ containerPort: 80 }] }
  ]
}

apiVersion and kind are derived from the schema automatically:

deploy.to_h[:apiVersion]  # => "apps/v1"
deploy.to_h[:kind]        # => "Deployment"

Subclassing

class RailsApp < Kube::Schema["Deployment"]
  def default_image
    "ruby:3.4"
  end
end

app = RailsApp.new {
  metadata.name = "rails"
  spec.template.spec.containers = [
    { name: "web", image: "myapp:latest" }
  ]
}

app.default_image  # => "ruby:3.4"
app.valid?         # => true

Validation

Every resource validates against the full Kubernetes OpenAPI spec.

deploy.valid?   # => true

bad = Kube::Schema["Deployment"].new {
  self.apiVersion = 12345
}

bad.valid?   # => false

bad.valid!
# Kube::ValidationError: Schema validation failed for Deployment:
#   - apiVersion = 12345 — expected string, got Integer

to_yaml refuses to serialize invalid resources:

bad.to_yaml  # raises Kube::ValidationError

Error messages

Validation errors render annotated YAML with color-coded diagnostics. Error lines are highlighted in red, missing required keys are injected inline, and each problem gets a clear explanation:

Validation error output

Manifests

Group resources into multi-document YAML.

manifest = Kube::Schema::Manifest.new

manifest << Kube::Schema["Namespace"].new {
  metadata.name = "prod"
}

manifest << Kube::Schema["Deployment"].new {
  metadata.name = "web"
  metadata.namespace = "prod"
  spec.replicas = 3
  spec.template.spec.containers = [
    { name: "app", image: "nginx:1.27" }
  ]
}

manifest << Kube::Schema["Service"].new {
  metadata.name = "web"
  metadata.namespace = "prod"
  spec.selector = { app: "web" }
  spec.ports = [{ port: 80, targetPort: 8080 }]
}

puts manifest.to_yaml
---
apiVersion: v1
kind: Namespace
metadata:
  name: prod
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: prod
spec:
  replicas: 3
  ...
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: prod
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080

File I/O

# Write
manifest.write("cluster.yaml")

# Read
loaded = Kube::Schema::Manifest.open("cluster.yaml")

# Modify and write back
loaded << new_resource
loaded.write

Composition

Manifests flatten into each other. No nesting.

infra = Kube::Schema::Manifest.new(namespace, configmap, secret)
app   = Kube::Schema::Manifest.new(deployment, service)

combined = Kube::Schema::Manifest.new
combined << infra
combined << app
combined.size  # => 5

Enumerable

manifest.map { |r| r.to_h[:kind] }
manifest.select { |r| r.to_h[:kind] == "Service" }
manifest.any? { |r| r.to_h[:kind] == "Pod" }
manifest.count

Schema versions

Bundled schemas ship with the gem for Kubernetes 1.19 through 1.35. Updated automatically via CI.

Kube::Schema.schema_version = "1.31"
Kube::Schema["Deployment"]  # uses 1.31

Related projects

  • kube_cluster -- OOP resource management with dirty tracking and persistence
  • kube_kubectl -- Ruby DSL that compiles to kubectl and helm commands
  • kube_kit -- Generators for kube_cluster projects
  • kube_engine -- Kubernetes engine

Built with

black_hole_struct -- the dynamic nested struct that powers the block DSL. json_schemer -- JSON Schema validation against the real Kubernetes OpenAPI specs.