Skip to content

Commit b048426

Browse files
dtfranzPer Goncalves da Silva
authored andcommitted
Correct CRD validation to validate CRs against each version of the updated CRD
Upstream-repository: operator-lifecycle-manager Upstream-commit: 242f63f Signed-off-by: Daniel Franz <dfranz@redhat.com> Signed-off-by: Per Goncalves da Silva <pegoncal@redhat.com>
1 parent f493a75 commit b048426

20 files changed

Lines changed: 772 additions & 159 deletions

File tree

staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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
18521852
func 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)
18871878
func 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

Comments
 (0)