@@ -18,7 +18,6 @@ package validate
1818
1919import (
2020 "context"
21- "fmt"
2221
2322 ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2423 extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -60,41 +59,29 @@ func SchemaValidate(ctx context.Context, resources []*unstructured.Unstructured,
6059 return nil , errors .Wrap (err , "cannot create schema validators" )
6160 }
6261
63- // Index old resources by GVK+name+namespace so each resource can be paired
64- // with its previous state. On duplicate keys the last entry wins.
65- oldByKey := make (map [string ]* unstructured.Unstructured , len (oldResources ))
66- for _ , o := range oldResources {
67- oldByKey [resourceKey (o )] = o
68- }
69-
7062 result := & ValidationResult {
7163 Resources : make ([]ResourceValidationResult , 0 , len (resources )),
7264 }
7365 for _ , r := range resources {
74- result .Resources = append (result .Resources , validateResource (ctx , r , objectOf (oldByKey [resourceKey (r )]), schemaValidators , structurals , crds ))
66+ // Find this resource's previous state, if supplied, so CEL transition
67+ // rules (those referencing oldSelf) can be evaluated. A resource is
68+ // matched to its old state by GroupVersionKind, namespace, and name;
69+ // with no match oldObject stays nil and transition rules are skipped,
70+ // exactly as on a Kubernetes create.
71+ gvk , namespace , name := r .GroupVersionKind (), r .GetNamespace (), getResourceName (r )
72+ var oldObject map [string ]any
73+ for _ , o := range oldResources {
74+ if o .GroupVersionKind () == gvk && o .GetNamespace () == namespace && getResourceName (o ) == name {
75+ oldObject = o .Object
76+ break
77+ }
78+ }
79+ result .Resources = append (result .Resources , validateResource (ctx , r , oldObject , schemaValidators , structurals , crds ))
7580 }
7681 result .Summary = computeSummary (result .Resources )
7782 return result , nil
7883}
7984
80- // resourceKey identifies a resource by GroupVersionKind, name, and namespace.
81- // It is used to match a resource under validation to its previous state so CEL
82- // transition rules see the right old object.
83- func resourceKey (r * unstructured.Unstructured ) string {
84- gvk := r .GetObjectKind ().GroupVersionKind ()
85- return fmt .Sprintf ("%s-%s-%s" , gvk .String (), getResourceName (r ), r .GetNamespace ())
86- }
87-
88- // objectOf returns the unstructured content of u, or nil when u is nil. It
89- // keeps the nil check for an unmatched old resource in one place so callers can
90- // pass the result straight to the CEL validator's oldObject argument.
91- func objectOf (u * unstructured.Unstructured ) map [string ]any {
92- if u == nil {
93- return nil
94- }
95- return u .Object
96- }
97-
9885// validateResource runs every check (schema, CEL, unknown fields, defaulting)
9986// against a single resource and returns its ResourceValidationResult. It is
10087// the per-resource decomposition of SchemaValidate; pulling it out keeps the
0 commit comments