Skip to content

Schemas#

The schema is a required part of a CustomResource because the apiserver only accepts a CustomResourceDefinition with a valid schema.

There are three main ways to get a schema injected into the CustomResourceDefinition.

Using JsonSchema#

The JsonSchema proc macro from schemars is what gives a struct the ability to produce a schema. By default, a struct must impl JsonSchema to be able to derive CustomResource.

In both derive mode (default) and manual mode, kube-derive forces an impl JsonSchema requirement. This impl is then used by kube-derive with our own conformance rewriter for structural schemas.

When using JsonSchema, your generated CustomResourceDefinition (via CustomResourceExt) will contain a schema.

Deriving JsonSchema#

The default setting uses #[derive(JsonSchema)], and kube-derive will propagate this derive to the generated Kubernetes struct.

This requires #[derive(CustomResource, JsonSchema)] on the spec struct:

#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[kube(kind = "Document", group = "kube.rs", version = "v1", namespaced)]
pub struct DocumentSpec {
    pub title: String,
    pub hide: bool,
    pub content: String,
}

This example (simplified variant from controller-rs) generates a CustomResourceDefinition whose yaml representation (including schema) can be serialized using serde_yaml::to_string(&Document::crd())? and will output:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: documents.kube.rs
spec:
  group: kube.rs
  names:
    categories: []
    kind: Document
    plural: documents
    shortNames: []
    singular: document
  scope: Namespaced
  versions:
  - additionalPrinterColumns: []
    name: v1
    schema:
      openAPIV3Schema:
        description: Auto-generated derived type for DocumentSpec via `CustomResource`
        properties:
          spec:
            properties:
              content:
                type: string
              hide:
                type: boolean
              title:
                type: string
            required:
            - content
            - hide
            - title
            type: object
        required:
        - spec
        title: Document
        type: object
    served: true
    storage: true
    subresources: {}

See object#installation for a common pattern for generating this.

Schema requirements are transitive

If your spec struct tries to derive JsonSchema, then all its members must also derive JsonSchema.

See examples/crd_derive_schema.

Implementing JsonSchema#

When using #[kube(schema = "manual")], kube-derive will not insert the derive attr of JsonSchema on the generated struct, and you are expected to provide an impl JsonSchema for GeneratedStruct yourself.

This allows filling the gaps if your struct members only has partial JsonSchema coverage.

See examples/crd_derive_custom_schema.

Overriding Members#

When you are implementing or deriving JsonSchema, you can overriding specific parts of a JsonSchema schema using #[schemars(schema_with)]. Some specific examples:

Disabling Schemas#

When using #[kube(schema = "disabled)], you are telling kube-derive not to use schemars at all, and you are taking responsibility for creating the schema manually. This removes all the safety mechanisms, and requires manually patching the schema fields, and dealing with structural schema quirks yourself.

Disabling schemas invalidates the generated CRD

Setting this option means the CustomResourceDefinition provided by CustomResourceExt will require modification.

Any manual schemas must be attached on the generated CustomResourceDefinition before use. An example of this can be found in examples/crd_derive_no_schema.

The main reason for going down this approach is if you are porting a controller with a CRD from another language and you want to 100% conformance to the existing schema out of the gate.

This method allows eliding the #[derive(JsonSchema)] instruction, and possibly also schemars from the dependency tree if you are careful with features.

Versioning#

It is possible to progress between two structs deriving CustomResource in a versioned manner.

You can define multiple structs within versioned modules ala https://github.com/kube-rs/kube/blob/main/examples/crd_derive_multi.rs and then use merge_crds to combine them.

See CustomResource#versioning, and upstream docs on Versions in CustomResourceDefinitions for more info.

Validation#

Kubernetes >1.25 supports including validation rules in the openapi schema, and there are a couple of ways to include these. See admission#Validation Using CEL Validation for examples.

Manual Rules#

This can be done by following upstream docs, and manually #Implementing-JsonSchema or #Overriding-Members to inject validation rules into specific parts of the schema.

This approach will let you use the 1.25 Common Expression Language feature.
There are currently no recommended ways of doing client-side validation with this approach, but there are new cel parser/interpreter crates and a cel expression playground that might be useful here.

Deriving via Garde#

Using garde is a nice for the simple case because it allows doing both client-side validation, and server-side validation, with the caveat that it only works on both sides for basic validation rules as schemars can only pick up on some of them.

See CustomResource#schema-validation.