@@ -30,13 +30,13 @@ import (
3030 "k8s.io/apimachinery/pkg/selection"
3131 utilerrors "k8s.io/apimachinery/pkg/util/errors"
3232 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
33+ "k8s.io/apimachinery/pkg/util/sets"
3334 "k8s.io/apimachinery/pkg/util/validation/field"
3435 "k8s.io/apimachinery/pkg/util/yaml"
3536 "k8s.io/client-go/dynamic"
3637 "k8s.io/client-go/informers"
3738 "k8s.io/client-go/tools/cache"
3839 "k8s.io/client-go/tools/clientcmd"
39- "k8s.io/client-go/tools/pager"
4040 "k8s.io/client-go/tools/record"
4141 "k8s.io/client-go/util/retry"
4242 "k8s.io/client-go/util/workqueue"
@@ -1852,96 +1852,109 @@ func transitionInstallPlanState(log logrus.FieldLogger, transitioner installPlan
18521852func validateV1CRDCompatibility (dynamicClient dynamic.Interface , oldCRD * apiextensionsv1.CustomResourceDefinition , newCRD * apiextensionsv1.CustomResourceDefinition ) error {
18531853 logrus .Debugf ("Comparing %#v to %#v" , oldCRD .Spec .Versions , newCRD .Spec .Versions )
18541854
1855- // If validation schema is unchanged, return right away
1856- newestSchema := newCRD .Spec .Versions [len (newCRD .Spec .Versions )- 1 ].Schema
1857- for i , oldVersion := range oldCRD .Spec .Versions {
1858- if ! reflect .DeepEqual (oldVersion .Schema , newestSchema ) {
1859- break
1860- }
1861- if i == len (oldCRD .Spec .Versions )- 1 {
1862- // we are on the last iteration
1863- // schema has not changed between versions at this point.
1864- return nil
1855+ oldVersionSet := sets .New [string ]()
1856+ for _ , oldVersion := range oldCRD .Spec .Versions {
1857+ if ! oldVersionSet .Has (oldVersion .Name ) && oldVersion .Served {
1858+ oldVersionSet .Insert (oldVersion .Name )
18651859 }
18661860 }
18671861
1868- convertedCRD := & apiextensions.CustomResourceDefinition {}
1869- if err := apiextensionsv1 .Convert_v1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition (newCRD , convertedCRD , nil ); err != nil {
1870- return err
1871- }
1872- for _ , version := range oldCRD .Spec .Versions {
1873- if version .Served {
1874- gvr := schema.GroupVersionResource {Group : oldCRD .Spec .Group , Version : version .Name , Resource : oldCRD .Spec .Names .Plural }
1875- err := validateExistingCRs (dynamicClient , gvr , convertedCRD )
1876- if err != nil {
1862+ validationsMap := make (map [string ]* apiextensions.CustomResourceValidation , 0 )
1863+ for _ , newVersion := range newCRD .Spec .Versions {
1864+ if oldVersionSet .Has (newVersion .Name ) && newVersion .Served {
1865+ // If the new CRD's version is present in the cluster and still
1866+ // served then fill the map entry with the new validation
1867+ convertedValidation := & apiextensions.CustomResourceValidation {}
1868+ if err := apiextensionsv1 .Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation (newVersion .Schema , convertedValidation , nil ); err != nil {
18771869 return err
18781870 }
1871+ validationsMap [newVersion .Name ] = convertedValidation
18791872 }
18801873 }
1881-
1882- logrus .Debugf ("Successfully validated CRD %s\n " , newCRD .Name )
1883- return nil
1874+ return validateExistingCRs (dynamicClient , schema.GroupResource {Group : newCRD .Spec .Group , Resource : newCRD .Spec .Names .Plural }, validationsMap )
18841875}
18851876
18861877// Validate all existing served versions against new CRD's validation (if changed)
18871878func validateV1Beta1CRDCompatibility (dynamicClient dynamic.Interface , oldCRD * apiextensionsv1beta1.CustomResourceDefinition , newCRD * apiextensionsv1beta1.CustomResourceDefinition ) error {
18881879 logrus .Debugf ("Comparing %#v to %#v" , oldCRD .Spec .Validation , newCRD .Spec .Validation )
1889-
1890- // TODO return early of all versions are equal
1891- convertedCRD := & apiextensions.CustomResourceDefinition {}
1892- if err := apiextensionsv1beta1 .Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition (newCRD , convertedCRD , nil ); err != nil {
1893- return err
1880+ oldVersionSet := sets .New [string ]()
1881+ if len (oldCRD .Spec .Versions ) == 0 {
1882+ // apiextensionsv1beta1 special case: if spec.Versions is empty, use the global version and validation
1883+ oldVersionSet .Insert (oldCRD .Spec .Version )
18941884 }
1895- for _ , version := range oldCRD .Spec .Versions {
1896- if version .Served {
1897- gvr := schema.GroupVersionResource {Group : oldCRD .Spec .Group , Version : version .Name , Resource : oldCRD .Spec .Names .Plural }
1898- err := validateExistingCRs (dynamicClient , gvr , convertedCRD )
1899- if err != nil {
1900- return err
1901- }
1885+ for _ , oldVersion := range oldCRD .Spec .Versions {
1886+ // collect served versions from spec.Versions if the list is present
1887+ if ! oldVersionSet .Has (oldVersion .Name ) && oldVersion .Served {
1888+ oldVersionSet .Insert (oldVersion .Name )
19021889 }
19031890 }
19041891
1905- if oldCRD .Spec .Version != "" {
1906- gvr := schema.GroupVersionResource {Group : oldCRD .Spec .Group , Version : oldCRD .Spec .Version , Resource : oldCRD .Spec .Names .Plural }
1907- err := validateExistingCRs (dynamicClient , gvr , convertedCRD )
1908- if err != nil {
1909- return err
1892+ validationsMap := make (map [string ]* apiextensions.CustomResourceValidation , 0 )
1893+ gr := schema.GroupResource {Group : newCRD .Spec .Group , Resource : newCRD .Spec .Names .Plural }
1894+ if len (newCRD .Spec .Versions ) == 0 {
1895+ // apiextensionsv1beta1 special case: if spec.Versions of newCRD is empty, use the global version and validation
1896+ if oldVersionSet .Has (newCRD .Spec .Version ) {
1897+ convertedValidation := & apiextensions.CustomResourceValidation {}
1898+ if err := apiextensionsv1beta1 .Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation (newCRD .Spec .Validation , convertedValidation , nil ); err != nil {
1899+ return err
1900+ }
1901+ validationsMap [newCRD .Spec .Version ] = convertedValidation
1902+ }
1903+ }
1904+ for _ , newVersion := range newCRD .Spec .Versions {
1905+ if oldVersionSet .Has (newVersion .Name ) && newVersion .Served {
1906+ // If the new CRD's version is present in the cluster and still
1907+ // served then fill the map entry with the new validation
1908+ if newCRD .Spec .Validation != nil {
1909+ // apiextensionsv1beta1 special case: spec.Validation and spec.Versions[].Schema are mutually exclusive;
1910+ // if spec.Versions is non-empty and spec.Validation is set then we can validate once against any
1911+ // single existing version.
1912+ convertedValidation := & apiextensions.CustomResourceValidation {}
1913+ if err := apiextensionsv1beta1 .Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation (newCRD .Spec .Validation , convertedValidation , nil ); err != nil {
1914+ return err
1915+ }
1916+ return validateExistingCRs (dynamicClient , gr , map [string ]* apiextensions.CustomResourceValidation {newVersion .Name : convertedValidation })
1917+ }
1918+ convertedValidation := & apiextensions.CustomResourceValidation {}
1919+ if err := apiextensionsv1beta1 .Convert_v1beta1_CustomResourceValidation_To_apiextensions_CustomResourceValidation (newVersion .Schema , convertedValidation , nil ); err != nil {
1920+ return err
1921+ }
1922+ validationsMap [newVersion .Name ] = convertedValidation
19101923 }
19111924 }
1912- logrus .Debugf ("Successfully validated CRD %s\n " , newCRD .Name )
1913- return nil
1925+ return validateExistingCRs (dynamicClient , gr , validationsMap )
19141926}
19151927
1916- func validateExistingCRs (dynamicClient dynamic.Interface , gvr schema.GroupVersionResource , newCRD * apiextensions.CustomResourceDefinition ) error {
1917- pager := pager .New (pager .SimplePageFunc (func (opts metav1.ListOptions ) (runtime.Object , error ) {
1918- return dynamicClient .Resource (gvr ).List (context .TODO (), opts )
1919- }))
1920- validationFn := func (obj runtime.Object ) error {
1921- // lister will only provide unstructured objects as runtime.Object, so this should never fail to convert
1922- // if it does, it's a programming error
1923- cr := obj .(* unstructured.Unstructured )
1924- validator , _ , err := validation .NewSchemaValidator (newCRD .Spec .Validation )
1928+ // validateExistingCRs lists all CRs for each version entry in validationsMap, then validates each using the paired validation.
1929+ func validateExistingCRs (dynamicClient dynamic.Interface , gr schema.GroupResource , validationsMap map [string ]* apiextensions.CustomResourceValidation ) error {
1930+ for version , schemaValidation := range validationsMap {
1931+ // create validator from given crdValidation
1932+ validator , _ , err := validation .NewSchemaValidator (schemaValidation )
19251933 if err != nil {
1926- return fmt .Errorf ("error creating validator for schema %#v : %s" , newCRD . Spec . Validation , err )
1934+ return fmt .Errorf ("error creating validator for schema version %s : %s" , version , err )
19271935 }
1928- err = validation .ValidateCustomResource (field .NewPath ("" ), cr .UnstructuredContent (), validator ).ToAggregate ()
1936+
1937+ gvr := schema.GroupVersionResource {Group : gr .Group , Version : version , Resource : gr .Resource }
1938+ crList , err := dynamicClient .Resource (gvr ).List (context .TODO (), metav1.ListOptions {})
19291939 if err != nil {
1930- var namespacedName string
1931- if cr .GetNamespace () == "" {
1932- namespacedName = cr .GetName ()
1933- } else {
1934- namespacedName = fmt .Sprintf ("%s/%s" , cr .GetNamespace (), cr .GetName ())
1940+ return fmt .Errorf ("error listing resources in GroupVersionResource %#v: %s" , gvr , err )
1941+ }
1942+
1943+ // validate each CR against this version schema
1944+ for _ , cr := range crList .Items {
1945+ err = validation .ValidateCustomResource (field .NewPath ("" ), cr .UnstructuredContent (), validator ).ToAggregate ()
1946+ if err != nil {
1947+ var namespacedName string
1948+ if cr .GetNamespace () == "" {
1949+ namespacedName = cr .GetName ()
1950+ } else {
1951+ namespacedName = fmt .Sprintf ("%s/%s" , cr .GetNamespace (), cr .GetName ())
1952+ }
1953+ return fmt .Errorf ("error validating %s %q: updated validation is too restrictive: %v" , cr .GroupVersionKind (), namespacedName , err )
19351954 }
1936- return fmt .Errorf ("error validating %s %q: updated validation is too restrictive: %v" , cr .GroupVersionKind (), namespacedName , err )
19371955 }
19381956 return nil
19391957 }
1940- err := pager .EachListItem (context .Background (), metav1.ListOptions {}, validationFn )
1941- if err != nil {
1942- return err
1943- }
1944-
19451958 return nil
19461959}
19471960
0 commit comments