Skip to content

Commit 3b383b7

Browse files
Merge pull request #973 from dinhxuanvu/crd-versions-fix
Bug 1732914: Operator upgrades fail when versions field is not set
2 parents 7d6665d + fcb5719 commit 3b383b7

File tree

3 files changed

+92
-15
lines changed

3 files changed

+92
-15
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,23 +1106,35 @@ func (o *Operator) ResolvePlan(plan *v1alpha1.InstallPlan) error {
11061106
return nil
11071107
}
11081108

1109-
// Ensure all existing versions are present in new CRD
1109+
// Ensure all existing served versions are present in new CRD
11101110
func ensureCRDVersions(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
1111+
newCRDVersions := map[string]struct{}{}
1112+
1113+
for _, newVersion := range newCRD.Spec.Versions {
1114+
newCRDVersions[newVersion.Name] = struct{}{}
1115+
}
1116+
if newCRD.Spec.Version != "" {
1117+
newCRDVersions[newCRD.Spec.Version] = struct{}{}
1118+
}
1119+
11111120
for _, oldVersion := range oldCRD.Spec.Versions {
1112-
var versionPresent bool
1113-
for _, newVersion := range newCRD.Spec.Versions {
1114-
if oldVersion.Name == newVersion.Name {
1115-
versionPresent = true
1121+
if oldVersion.Served {
1122+
_, ok := newCRDVersions[oldVersion.Name]
1123+
if !ok {
1124+
return fmt.Errorf("New CRD (%s) must contain existing served versions (%s)", oldCRD.Name, oldVersion.Name)
11161125
}
11171126
}
1118-
if !versionPresent {
1119-
return fmt.Errorf("not allowing CRD (%v) update with unincluded version %v", newCRD.GetName(), oldVersion)
1127+
}
1128+
if oldCRD.Spec.Version != "" {
1129+
_, ok := newCRDVersions[oldCRD.Spec.Version]
1130+
if !ok {
1131+
return fmt.Errorf("New CRD (%s) must contain existing version (%s)", oldCRD.Name, oldCRD.Spec.Version)
11201132
}
11211133
}
1122-
11231134
return nil
11241135
}
11251136

1137+
// Validate all existing served versions against new CRD's validation (if changed)
11261138
func (o *Operator) validateCustomResourceDefinition(oldCRD *v1beta1ext.CustomResourceDefinition, newCRD *v1beta1ext.CustomResourceDefinition) error {
11271139
o.logger.Debugf("Comparing %#v to %#v", oldCRD.Spec.Validation, newCRD.Spec.Validation)
11281140
// If validation schema is unchanged, return right away
@@ -1133,11 +1145,13 @@ func (o *Operator) validateCustomResourceDefinition(oldCRD *v1beta1ext.CustomRes
11331145
if err := v1beta1ext.Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(newCRD, convertedCRD, nil); err != nil {
11341146
return err
11351147
}
1136-
for _, oldVersion := range oldCRD.Spec.Versions {
1137-
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: oldVersion.Name, Resource: oldCRD.Spec.Names.Plural}
1138-
err := o.validateExistingCRs(gvr, convertedCRD)
1139-
if err != nil {
1140-
return err
1148+
for _, version := range oldCRD.Spec.Versions {
1149+
if !version.Served {
1150+
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: version.Name, Resource: oldCRD.Spec.Names.Plural}
1151+
err := o.validateExistingCRs(gvr, convertedCRD)
1152+
if err != nil {
1153+
return err
1154+
}
11411155
}
11421156
}
11431157

@@ -1148,7 +1162,7 @@ func (o *Operator) validateCustomResourceDefinition(oldCRD *v1beta1ext.CustomRes
11481162
return err
11491163
}
11501164
}
1151-
1165+
o.logger.Debugf("Successfully validated CRD %s\n", newCRD.Name)
11521166
return nil
11531167
}
11541168

@@ -1240,7 +1254,8 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
12401254
} else if len(matchedCSV) > 1 {
12411255
o.logger.Debugf("Found multiple owners for CRD %v", crd)
12421256

1243-
if err := ensureCRDVersions(currentCRD, &crd); err != nil {
1257+
err := ensureCRDVersions(currentCRD, &crd)
1258+
if err != nil {
12441259
return errorwrap.Wrapf(err, "error missing existing CRD version(s) in new CRD: %s", step.Resource.Name)
12451260
}
12461261

pkg/controller/operators/catalog/operator_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,35 @@ func TestEnsureCRDVersions(t *testing.T) {
196196
}(),
197197
expectedFailure: true,
198198
},
199+
{
200+
name: "missing version in new CRD",
201+
oldCRD: func() v1beta1.CustomResourceDefinition {
202+
oldCRD := crd(mainCRDPlural)
203+
oldCRD.Spec.Version = "v1alpha1"
204+
return oldCRD
205+
}(),
206+
newCRD: func() v1beta1.CustomResourceDefinition {
207+
newCRD := crd(mainCRDPlural)
208+
newCRD.Spec.Version = "v1alpha2"
209+
return newCRD
210+
}(),
211+
expectedFailure: true,
212+
},
213+
{
214+
name: "existing version is present in new CRD's versions",
215+
oldCRD: func() v1beta1.CustomResourceDefinition {
216+
oldCRD := crd(mainCRDPlural)
217+
oldCRD.Spec.Version = "v1alpha1"
218+
return oldCRD
219+
}(),
220+
newCRD: func() v1beta1.CustomResourceDefinition {
221+
newCRD := crd(mainCRDPlural)
222+
newCRD.Spec.Version = ""
223+
newCRD.Spec.Versions = addedVersions
224+
return newCRD
225+
}(),
226+
expectedFailure: false,
227+
},
199228
}
200229

201230
for _, tt := range tests {

test/e2e/installplan_e2e_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,39 @@ func TestInstallPlanWithCRDSchemaChange(t *testing.T) {
825825
return &newCRD
826826
}(),
827827
},
828+
{
829+
name: "existing version is present in new CRD (deprecated field)",
830+
expectedPhase: v1alpha1.InstallPlanPhaseComplete,
831+
oldCRD: func() *apiextensions.CustomResourceDefinition {
832+
oldCRD := newCRD(mainCRDPlural + "d")
833+
oldCRD.Spec.Version = "v1alpha1"
834+
return &oldCRD
835+
}(),
836+
newCRD: func() *apiextensions.CustomResourceDefinition {
837+
newCRD := newCRD(mainCRDPlural + "d")
838+
newCRD.Spec.Version = "v1alpha1"
839+
newCRD.Spec.Validation = &apiextensions.CustomResourceValidation{
840+
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
841+
Type: "object",
842+
Properties: map[string]apiextensions.JSONSchemaProps{
843+
"spec": {
844+
Type: "object",
845+
Description: "Spec of a test object.",
846+
Properties: map[string]apiextensions.JSONSchemaProps{
847+
"scalar": {
848+
Type: "number",
849+
Description: "Scalar value that should have a min and max.",
850+
Minimum: &min,
851+
Maximum: &max,
852+
},
853+
},
854+
},
855+
},
856+
},
857+
}
858+
return &newCRD
859+
}(),
860+
},
828861
}
829862

830863
for _, tt := range tests {

0 commit comments

Comments
 (0)