Skip to content

Commit af9b90d

Browse files
Merge pull request #902 from jpeeler/crd-schema-change
Allow CRD updates with multiple owners
2 parents d4505e7 + b5fde62 commit af9b90d

File tree

6 files changed

+373
-5
lines changed

6 files changed

+373
-5
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,44 @@ func (o *Operator) ResolvePlan(plan *v1alpha1.InstallPlan) error {
10321032
return nil
10331033
}
10341034

1035+
// TODO: in the future this will be extended to verify more than just whether or not the schema changed
1036+
func checkCRDSchemas(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) bool {
1037+
if reflect.DeepEqual(oldCRD.Spec.Validation, newCRD.Spec.Validation) {
1038+
return true
1039+
}
1040+
1041+
return false
1042+
}
1043+
1044+
func safeToUpdate(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1045+
shouldCheckSchema := len(oldCRD.Spec.Versions) == len(newCRD.Spec.Versions) // no version bump
1046+
1047+
// 1) if there's no version change, verify that schemas match 2) ensure all old versions are present
1048+
for _, oldVersion := range oldCRD.Spec.Versions {
1049+
var versionPresent bool
1050+
for _, newVersion := range newCRD.Spec.Versions {
1051+
if oldVersion.Name == newVersion.Name {
1052+
if shouldCheckSchema && !checkCRDSchemas(oldCRD, newCRD) {
1053+
return fmt.Errorf("not allowing CRD (%v) update with multiple owners with schema change on version %v", newCRD.GetName(), oldVersion)
1054+
}
1055+
versionPresent = true
1056+
}
1057+
}
1058+
if !versionPresent {
1059+
return fmt.Errorf("not allowing CRD (%v) update with unincluded version %v", newCRD.GetName(), oldVersion)
1060+
}
1061+
}
1062+
1063+
// ensure a CRD with multiple versions isn't checked
1064+
if newCRD.Spec.Version != "" {
1065+
if !checkCRDSchemas(oldCRD, newCRD) {
1066+
return fmt.Errorf("not allowing CRD (%v) update with multiple owners with schema change on (single) version %v", newCRD.GetName(), newCRD.Spec.Version)
1067+
}
1068+
}
1069+
return nil
1070+
1071+
}
1072+
10351073
// ExecutePlan applies a planned InstallPlan to a namespace.
10361074
func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10371075
if plan.Status.Phase != v1alpha1.InstallPlanPhaseInstalling {
@@ -1090,9 +1128,19 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10901128
if err != nil {
10911129
return errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
10921130
}
1131+
crd.SetResourceVersion(currentCRD.GetResourceVersion())
10931132
if len(matchedCSV) == 1 {
1094-
// Attempt to update CRD
1095-
crd.SetResourceVersion(currentCRD.GetResourceVersion())
1133+
o.logger.Debugf("Found one owner for CRD %v", crd)
1134+
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1135+
if err != nil {
1136+
return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
1137+
}
1138+
} else if len(matchedCSV) > 1 {
1139+
o.logger.Debugf("Found multiple owners for CRD %v", crd)
1140+
if err := safeToUpdate(currentCRD, &crd); err != nil {
1141+
return err
1142+
}
1143+
10961144
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
10971145
if err != nil {
10981146
return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name)

pkg/controller/operators/catalog/operator_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,99 @@ func TestTransitionInstallPlan(t *testing.T) {
125125
}
126126
}
127127

128+
func TestSafeToUpdate(t *testing.T) {
129+
mainCRDPlural := "ins-main-abcde"
130+
differentSchema := &v1beta1.CustomResourceValidation{
131+
OpenAPIV3Schema: &v1beta1.JSONSchemaProps{
132+
Properties: map[string]v1beta1.JSONSchemaProps{
133+
"spec": {
134+
Type: "object",
135+
Description: "Spec of a test object.",
136+
Properties: map[string]v1beta1.JSONSchemaProps{
137+
"scalar": {
138+
Type: "number",
139+
Description: "Scalar value that should have a min and max.",
140+
Minimum: func() *float64 {
141+
var min float64 = 2
142+
return &min
143+
}(),
144+
Maximum: func() *float64 {
145+
var max float64 = 256
146+
return &max
147+
}(),
148+
},
149+
},
150+
},
151+
},
152+
},
153+
}
154+
155+
differentVersions := []v1beta1.CustomResourceDefinitionVersion{
156+
{
157+
Name: "v1alpha1",
158+
Served: true,
159+
Storage: false,
160+
},
161+
{
162+
Name: "v1alpha2",
163+
Served: true,
164+
Storage: true,
165+
},
166+
}
167+
168+
tests := []struct {
169+
name string
170+
oldCRD v1beta1.CustomResourceDefinition
171+
newCRD v1beta1.CustomResourceDefinition
172+
expectedFailure bool
173+
}{
174+
{
175+
// in reality this would never happen within ExecutePlan, but fully exercises function
176+
name: "same version, same schema",
177+
oldCRD: crd(mainCRDPlural),
178+
newCRD: crd(mainCRDPlural),
179+
},
180+
{
181+
name: "same version, different schema",
182+
oldCRD: crd(mainCRDPlural),
183+
newCRD: func() v1beta1.CustomResourceDefinition {
184+
newCRD := crd(mainCRDPlural)
185+
newCRD.Spec.Validation = differentSchema
186+
return newCRD
187+
}(),
188+
expectedFailure: true,
189+
},
190+
{
191+
name: "different version, different schema",
192+
oldCRD: crd(mainCRDPlural),
193+
newCRD: func() v1beta1.CustomResourceDefinition {
194+
newCRD := crd(mainCRDPlural)
195+
newCRD.Spec.Version = ""
196+
newCRD.Spec.Versions = differentVersions
197+
newCRD.Spec.Validation = differentSchema
198+
return newCRD
199+
}(),
200+
},
201+
{
202+
name: "different version, same schema",
203+
oldCRD: crd(mainCRDPlural),
204+
newCRD: func() v1beta1.CustomResourceDefinition {
205+
newCRD := crd(mainCRDPlural)
206+
newCRD.Spec.Version = ""
207+
newCRD.Spec.Versions = differentVersions
208+
return newCRD
209+
}(),
210+
},
211+
}
212+
213+
for _, tt := range tests {
214+
err := safeToUpdate(&tt.oldCRD, &tt.newCRD)
215+
if tt.expectedFailure {
216+
require.Error(t, err)
217+
}
218+
}
219+
}
220+
128221
func TestExecutePlan(t *testing.T) {
129222
namespace := "ns"
130223

pkg/controller/operators/olm/operator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ func (a *Operator) ensureLabels(in *v1alpha1.ClusterServiceVersion, labelSets ..
16161616
return in, nil
16171617
}
16181618

1619-
a.logger.WithField("labels", merged).Error("Labels updated!")
1619+
a.logger.WithField("labels", merged).Info("Labels updated!")
16201620

16211621
out := in.DeepCopy()
16221622
out.SetLabels(merged)

0 commit comments

Comments
 (0)