Skip to main content

transition_rules/
transition_rules.rs

1//! Transition rules (`oldSelf`): validating an UPDATE against the previous state.
2//!
3//! A CRD rule that references `oldSelf` is a *transition rule*: it compares the
4//! incoming object to the stored one. The apiserver only runs transition rules on
5//! UPDATE (there is no previous state on CREATE), and so does kube-cel — pass the
6//! old object as the third argument to [`Validator::validate`]. On CREATE
7//! (`old = None`) transition rules are skipped; ordinary rules still run.
8//!
9//! This focuses on the UPDATE-only patterns (immutability, monotonic change). For
10//! a broad mixed-rule walkthrough see the `validate_crd` example.
11//!
12//! Run with: `cargo run --example transition_rules --features validation`
13
14use kube_cel::Validator;
15use serde_json::json;
16
17fn main() {
18    let schema = json!({
19        "type": "object",
20        "properties": {
21            "storageClass": {"type": "string"},
22            "replicas": {"type": "integer"}
23        },
24        "x-kubernetes-validations": [
25            // Immutable field: may never change once set.
26            {
27                "rule": "self.storageClass == oldSelf.storageClass",
28                "message": "storageClass is immutable"
29            },
30            // Monotonic: replicas may only grow (no scale-down).
31            {
32                "rule": "self.replicas >= oldSelf.replicas",
33                "message": "replicas cannot be decreased"
34            }
35        ]
36    });
37
38    let validator = Validator::new();
39    let stored = json!({"storageClass": "fast-ssd", "replicas": 3});
40
41    // CREATE: no previous state, so transition rules are skipped entirely.
42    let created = json!({"storageClass": "fast-ssd", "replicas": 3});
43    report("CREATE (old = None)", validator.validate(&schema, &created, None));
44
45    // UPDATE that respects both rules: same storageClass, scaling up.
46    let scale_up = json!({"storageClass": "fast-ssd", "replicas": 5});
47    report(
48        "UPDATE scale up 3->5",
49        validator.validate(&schema, &scale_up, Some(&stored)),
50    );
51
52    // UPDATE that violates the monotonic rule: scaling down.
53    let scale_down = json!({"storageClass": "fast-ssd", "replicas": 1});
54    report(
55        "UPDATE scale down 3->1",
56        validator.validate(&schema, &scale_down, Some(&stored)),
57    );
58
59    // UPDATE that mutates an immutable field.
60    let restorage = json!({"storageClass": "cheap-hdd", "replicas": 3});
61    report(
62        "UPDATE change storageClass",
63        validator.validate(&schema, &restorage, Some(&stored)),
64    );
65}
66
67fn report(label: &str, errors: Vec<kube_cel::ValidationError>) {
68    if errors.is_empty() {
69        println!("{label}: OK");
70    } else {
71        println!("{label}: {} error(s)", errors.len());
72        for err in &errors {
73            println!("  - {}", err.message);
74        }
75    }
76}