@@ -14,13 +14,18 @@ import (
14
14
"github.com/sirupsen/logrus"
15
15
corev1 "k8s.io/api/core/v1"
16
16
rbacv1 "k8s.io/api/rbac/v1"
17
+ "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
17
18
v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
19
+ validation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
18
20
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
19
21
k8serrors "k8s.io/apimachinery/pkg/api/errors"
20
22
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
23
+ conversion "k8s.io/apimachinery/pkg/conversion"
21
24
"k8s.io/apimachinery/pkg/labels"
25
+ "k8s.io/apimachinery/pkg/runtime/schema"
22
26
utilclock "k8s.io/apimachinery/pkg/util/clock"
23
27
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
28
+ "k8s.io/client-go/dynamic"
24
29
"k8s.io/client-go/informers"
25
30
"k8s.io/client-go/tools/cache"
26
31
"k8s.io/client-go/tools/clientcmd"
@@ -64,6 +69,7 @@ type Operator struct {
64
69
clock utilclock.Clock
65
70
opClient operatorclient.ClientInterface
66
71
client versioned.Interface
72
+ dynamicClient dynamic.Interface
67
73
lister operatorlister.OperatorLister
68
74
catsrcQueueSet * queueinformer.ResourceQueueSet
69
75
subQueueSet * queueinformer.ResourceQueueSet
@@ -98,6 +104,12 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
98
104
return nil , err
99
105
}
100
106
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
+
101
113
// Create a new queueinformer-based operator.
102
114
opClient := operatorclient .NewClientFromConfig (kubeconfigPath , logger )
103
115
queueOperator , err := queueinformer .NewOperator (opClient .KubernetesInterface ().Discovery (), queueinformer .WithOperatorLogger (logger ))
@@ -114,6 +126,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
114
126
logger : logger ,
115
127
clock : clock ,
116
128
opClient : opClient ,
129
+ dynamicClient : dynamicClient ,
117
130
client : crClient ,
118
131
lister : lister ,
119
132
namespace : operatorNamespace ,
@@ -1067,7 +1080,51 @@ func safeToUpdate(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ex
1067
1080
}
1068
1081
}
1069
1082
return nil
1083
+ }
1070
1084
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
1071
1128
}
1072
1129
1073
1130
// ExecutePlan applies a planned InstallPlan to a namespace.
@@ -1131,6 +1188,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
1131
1188
crd .SetResourceVersion (currentCRD .GetResourceVersion ())
1132
1189
if len (matchedCSV ) == 1 {
1133
1190
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
+ }
1134
1194
_ , err = o .opClient .ApiextensionsV1beta1Interface ().ApiextensionsV1beta1 ().CustomResourceDefinitions ().Update (& crd )
1135
1195
if err != nil {
1136
1196
return errorwrap .Wrapf (err , "error updating CRD: %s" , step .Resource .Name )
@@ -1140,7 +1200,9 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
1140
1200
if err := safeToUpdate (currentCRD , & crd ); err != nil {
1141
1201
return err
1142
1202
}
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
+ }
1144
1206
_ , err = o .opClient .ApiextensionsV1beta1Interface ().ApiextensionsV1beta1 ().CustomResourceDefinitions ().Update (& crd )
1145
1207
if err != nil {
1146
1208
return errorwrap .Wrapf (err , "error update CRD: %s" , step .Resource .Name )
0 commit comments