Skip to content

Commit 92b4a2f

Browse files
committed
Validate existing Custom Resources against new schema prior updating
Allow CRD update if: 1. Now existing CRs found 2. Existing CRs is validated against new CRD schema Signed-off-by: Vu Dinh <[email protected]>
1 parent e16488d commit 92b4a2f

File tree

5 files changed

+549
-3
lines changed

5 files changed

+549
-3
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@ import (
1414
"github.com/sirupsen/logrus"
1515
corev1 "k8s.io/api/core/v1"
1616
rbacv1 "k8s.io/api/rbac/v1"
17+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
1718
v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
19+
validation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
1820
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1921
k8serrors "k8s.io/apimachinery/pkg/api/errors"
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23+
conversion "k8s.io/apimachinery/pkg/conversion"
2124
"k8s.io/apimachinery/pkg/labels"
25+
"k8s.io/apimachinery/pkg/runtime/schema"
2226
utilclock "k8s.io/apimachinery/pkg/util/clock"
2327
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
28+
"k8s.io/client-go/dynamic"
2429
"k8s.io/client-go/informers"
2530
"k8s.io/client-go/tools/cache"
2631
"k8s.io/client-go/tools/clientcmd"
@@ -64,6 +69,7 @@ type Operator struct {
6469
clock utilclock.Clock
6570
opClient operatorclient.ClientInterface
6671
client versioned.Interface
72+
dynamicClient dynamic.Interface
6773
lister operatorlister.OperatorLister
6874
catsrcQueueSet *queueinformer.ResourceQueueSet
6975
subQueueSet *queueinformer.ResourceQueueSet
@@ -98,6 +104,12 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
98104
return nil, err
99105
}
100106

107+
// Create a new client for dynamic types (CRs)
108+
dynamicClient, err := dynamic.NewForConfig(config)
109+
if err != nil {
110+
return nil, err
111+
}
112+
101113
// Create a new queueinformer-based operator.
102114
opClient := operatorclient.NewClientFromConfig(kubeconfigPath, logger)
103115
queueOperator, err := queueinformer.NewOperator(opClient.KubernetesInterface().Discovery(), queueinformer.WithOperatorLogger(logger))
@@ -114,6 +126,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
114126
logger: logger,
115127
clock: clock,
116128
opClient: opClient,
129+
dynamicClient: dynamicClient,
117130
client: crClient,
118131
lister: lister,
119132
namespace: operatorNamespace,
@@ -1067,7 +1080,51 @@ func safeToUpdate(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ex
10671080
}
10681081
}
10691082
return nil
1083+
}
10701084

1085+
func (o *Operator) validateCustomResourceDefinition(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1086+
o.logger.Debugf("Comparing %#v to %#v", oldCRD.Spec.Validation, newCRD.Spec.Validation)
1087+
var convertedCRD *apiextensions.CustomResourceDefinition
1088+
var scope conversion.Scope
1089+
if err := v1beta1ext.Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(newCRD, convertedCRD, scope); err != nil {
1090+
return err
1091+
}
1092+
for _, oldVersion := range oldCRD.Spec.Versions {
1093+
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: oldVersion.Name, Resource: newCRD.Spec.Names.Plural}
1094+
err := o.validateExistingCRs(gvr, convertedCRD)
1095+
if err != nil {
1096+
return err
1097+
}
1098+
}
1099+
1100+
if oldCRD.Spec.Version != "" {
1101+
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: oldCRD.Spec.Version, Resource: newCRD.Spec.Names.Plural}
1102+
err := o.validateExistingCRs(gvr, convertedCRD)
1103+
if err != nil {
1104+
return err
1105+
}
1106+
}
1107+
1108+
return nil
1109+
}
1110+
1111+
func (o *Operator) validateExistingCRs(gvr schema.GroupVersionResource, newCRD *apiextensions.CustomResourceDefinition) error {
1112+
crList, err := o.dynamicClient.Resource(gvr).List(metav1.ListOptions{})
1113+
if err != nil {
1114+
return fmt.Errorf("error listing resources in GroupVersionResource %#v: %s", gvr, err)
1115+
}
1116+
for _, cr := range crList.Items {
1117+
validator, _, err := validation.NewSchemaValidator(newCRD.Spec.Validation)
1118+
if err != nil {
1119+
return fmt.Errorf("error creating validator for schema %#v: %s", newCRD.Spec.Validation, err)
1120+
}
1121+
err = validation.ValidateCustomResource(cr, validator)
1122+
if err != nil {
1123+
return fmt.Errorf("error validating custom resource against new schema %#v: %s", newCRD.Spec.Validation, err)
1124+
}
1125+
}
1126+
1127+
return nil
10711128
}
10721129

10731130
// ExecutePlan applies a planned InstallPlan to a namespace.
@@ -1131,6 +1188,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11311188
crd.SetResourceVersion(currentCRD.GetResourceVersion())
11321189
if len(matchedCSV) == 1 {
11331190
o.logger.Debugf("Found one owner for CRD %v", crd)
1191+
if err = o.validateCustomResourceDefinition(currentCRD, &crd); err != nil {
1192+
return errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name)
1193+
}
11341194
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
11351195
if err != nil {
11361196
return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
@@ -1140,7 +1200,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
11401200
if err := safeToUpdate(currentCRD, &crd); err != nil {
11411201
return err
11421202
}
1143-
1203+
if err = o.validateCustomResourceDefinition(currentCRD, &crd); err != nil {
1204+
return errorwrap.Wrapf(err, "error validating existing CRs agains new CRD's schema: %s", step.Resource.Name)
1205+
}
11441206
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
11451207
if err != nil {
11461208
return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name)

vendor/k8s.io/client-go/dynamic/interface.go

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/k8s.io/client-go/dynamic/scheme.go

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)