Skip to main content

kube_workflow/
kube_workflow.rs

1//! The headline use case: client-side CRD validation inside a kube-rs workflow.
2//!
3//! A controller (or an admission webhook) often builds an object and is about to
4//! `apply`/`patch` it to the cluster. Running the CRD's `x-kubernetes-validations`
5//! rules *before* the round-trip catches invalid objects locally, surfaces a clear
6//! reason on the resource status, and avoids a guaranteed apiserver rejection.
7//!
8//! This example is intentionally dependency-light: it mocks the two things a real
9//! controller would *fetch* — the CRD's OpenAPI schema and the object under
10//! reconciliation — with `serde_json`, and marks each spot where a `kube::Api`
11//! call would slot in with a comment. That keeps `cargo run` fast and free of the
12//! heavy `kube`/`k8s-openapi` build, while showing exactly where validation fits.
13//!
14//! Run with: `cargo run --example kube_workflow --features validation`
15
16use kube_cel::Validator;
17use serde_json::{Value, json};
18
19fn main() {
20    // In a real controller this comes from the cluster, e.g.:
21    //     use kube::{Api, Client};
22    //     use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition;
23    //     let crds: Api<CustomResourceDefinition> = Api::all(client);
24    //     let crd = crds.get("widgets.example.com").await?;
25    //     let schema = crd.spec.versions[0].schema.unwrap().open_api_v3_schema.unwrap();
26    // Here we mock that fetched OpenAPI v3 schema directly.
27    let schema = fetch_crd_schema();
28
29    let validator = Validator::new();
30
31    // A controller reconcile loop typically builds a *desired* object and applies
32    // it. We show two desired states: one valid, one that violates the CRD rules.
33    let desired_ok = json!({
34        "spec": { "replicas": 3, "image": "registry.example.com/widget:1.4.2" }
35    });
36    let desired_bad = json!({
37        "spec": { "replicas": 0, "image": "widget:latest" }
38    });
39
40    for (label, desired) in [("valid desired", &desired_ok), ("invalid desired", &desired_bad)] {
41        println!("=== reconcile: {label} ===");
42
43        // Client-side gate, run BEFORE touching the apiserver.
44        match validator.validate(&schema, desired, None) {
45            Ok(()) => {
46                println!("  validation passed -> apply to cluster");
47                // The real apply happens only on the happy path:
48                //     let widgets: Api<Widget> = Api::namespaced(client, "default");
49                //     widgets.patch("my-widget", &PatchParams::apply("my-controller"),
50                //                   &Patch::Apply(desired)).await?;
51            }
52            Err(errors) => {
53                println!("  validation failed -> skip apply, record on status:");
54                for err in &errors {
55                    println!("    [{}] {}", err.field_path, err.message);
56                }
57                // Instead of a doomed apply, the controller would surface the reason:
58                //     widgets.patch_status("my-widget", &pp,
59                //         &Patch::Merge(json!({"status": {"validationError": err.message}}))).await?;
60            }
61        }
62        println!();
63    }
64}
65
66/// Stand-in for the CRD schema a controller fetches from the apiserver.
67fn fetch_crd_schema() -> Value {
68    json!({
69        "type": "object",
70        "properties": {
71            "spec": {
72                "type": "object",
73                "x-kubernetes-validations": [
74                    {"rule": "self.replicas >= 1", "message": "replicas must be at least 1"},
75                    {
76                        "rule": "!self.image.endsWith(':latest')",
77                        "message": "pin the image to a concrete tag, not ':latest'"
78                    }
79                ],
80                "properties": {
81                    "replicas": {"type": "integer"},
82                    "image": {"type": "string"}
83                }
84            }
85        }
86    })
87}