Skip to content

Commit b5fde62

Browse files
author
Jeff Peeler
committed
feat(olm): allow CRD updates with multiple owners
Currently CRD updates aren't done for existing CRDs, so this enables that functionality in the cases of: - New CRD is a different non-existing version - New CRD is the same version without an openapi schema change
1 parent f06b3f2 commit b5fde62

File tree

3 files changed

+370
-2
lines changed

3 files changed

+370
-2
lines changed

pkg/controller/operators/catalog/operator.go

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

1024+
// TODO: in the future this will be extended to verify more than just whether or not the schema changed
1025+
func checkCRDSchemas(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) bool {
1026+
if reflect.DeepEqual(oldCRD.Spec.Validation, newCRD.Spec.Validation) {
1027+
return true
1028+
}
1029+
1030+
return false
1031+
}
1032+
1033+
func safeToUpdate(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1034+
shouldCheckSchema := len(oldCRD.Spec.Versions) == len(newCRD.Spec.Versions) // no version bump
1035+
1036+
// 1) if there's no version change, verify that schemas match 2) ensure all old versions are present
1037+
for _, oldVersion := range oldCRD.Spec.Versions {
1038+
var versionPresent bool
1039+
for _, newVersion := range newCRD.Spec.Versions {
1040+
if oldVersion.Name == newVersion.Name {
1041+
if shouldCheckSchema && !checkCRDSchemas(oldCRD, newCRD) {
1042+
return fmt.Errorf("not allowing CRD (%v) update with multiple owners with schema change on version %v", newCRD.GetName(), oldVersion)
1043+
}
1044+
versionPresent = true
1045+
}
1046+
}
1047+
if !versionPresent {
1048+
return fmt.Errorf("not allowing CRD (%v) update with unincluded version %v", newCRD.GetName(), oldVersion)
1049+
}
1050+
}
1051+
1052+
// ensure a CRD with multiple versions isn't checked
1053+
if newCRD.Spec.Version != "" {
1054+
if !checkCRDSchemas(oldCRD, newCRD) {
1055+
return fmt.Errorf("not allowing CRD (%v) update with multiple owners with schema change on (single) version %v", newCRD.GetName(), newCRD.Spec.Version)
1056+
}
1057+
}
1058+
return nil
1059+
1060+
}
1061+
10241062
// ExecutePlan applies a planned InstallPlan to a namespace.
10251063
func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10261064
if plan.Status.Phase != v1alpha1.InstallPlanPhaseInstalling {
@@ -1067,9 +1105,19 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10671105
if err != nil {
10681106
return errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
10691107
}
1108+
crd.SetResourceVersion(currentCRD.GetResourceVersion())
10701109
if len(matchedCSV) == 1 {
1071-
// Attempt to update CRD
1072-
crd.SetResourceVersion(currentCRD.GetResourceVersion())
1110+
o.logger.Debugf("Found one owner for CRD %v", crd)
1111+
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1112+
if err != nil {
1113+
return errorwrap.Wrapf(err, "error updating CRD: %s", step.Resource.Name)
1114+
}
1115+
} else if len(matchedCSV) > 1 {
1116+
o.logger.Debugf("Found multiple owners for CRD %v", crd)
1117+
if err := safeToUpdate(currentCRD, &crd); err != nil {
1118+
return err
1119+
}
1120+
10731121
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
10741122
if err != nil {
10751123
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
@@ -121,6 +121,99 @@ func TestTransitionInstallPlan(t *testing.T) {
121121
}
122122
}
123123

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

0 commit comments

Comments
 (0)