Skip to content

Commit 15f5ba3

Browse files
Merge pull request #1470 from exdx/fix/bug-1825330
Bug 1825330: support creating v1beta CRDs to avoid data loss
2 parents 280a2a6 + 695c67f commit 15f5ba3

File tree

12 files changed

+577
-581
lines changed

12 files changed

+577
-581
lines changed

cmd/olm/cleanup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func TestCleanupOwnerReferences(t *testing.T) {
478478
require.Equal(t, tt.expected.err, cleanupOwnerReferences(c, crc))
479479

480480
listOpts := metav1.ListOptions{}
481-
csvs, err := crc.OperatorsV1alpha1().ClusterServiceVersions(metav1.NamespaceAll).List(listOpts)
481+
csvs, err := crc.OperatorsV1alpha1().ClusterServiceVersions(metav1.NamespaceAll).List(context.TODO(), listOpts)
482482
require.NoError(t, err)
483483
require.ElementsMatch(t, tt.expected.csvs, csvs.Items)
484484

pkg/controller/operators/catalog/operator.go

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
rbacv1 "k8s.io/api/rbac/v1"
1818
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
1919
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
20+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2021
"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
2122
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -1305,30 +1306,6 @@ func (o *Operator) ResolvePlan(plan *v1alpha1.InstallPlan) error {
13051306
return nil
13061307
}
13071308

1308-
func GetCRDV1VersionsMap(crd *apiextensionsv1.CustomResourceDefinition) map[string]struct{} {
1309-
versionsMap := map[string]struct{}{}
1310-
1311-
for _, version := range crd.Spec.Versions {
1312-
versionsMap[version.Name] = struct{}{}
1313-
}
1314-
return versionsMap
1315-
}
1316-
1317-
// Ensure all existing served versions are present in new CRD
1318-
func EnsureCRDVersions(oldCRD *apiextensionsv1.CustomResourceDefinition, newCRD *apiextensionsv1.CustomResourceDefinition) error {
1319-
newCRDVersions := GetCRDV1VersionsMap(newCRD)
1320-
1321-
for _, oldVersion := range oldCRD.Spec.Versions {
1322-
if oldVersion.Served {
1323-
_, ok := newCRDVersions[oldVersion.Name]
1324-
if !ok {
1325-
return fmt.Errorf("New CRD (%s) must contain existing served versions (%s)", oldCRD.Name, oldVersion.Name)
1326-
}
1327-
}
1328-
}
1329-
return nil
1330-
}
1331-
13321309
// Validate all existing served versions against new CRD's validation (if changed)
13331310
func validateV1CRDCompatibility(dynamicClient dynamic.Interface, oldCRD *apiextensionsv1.CustomResourceDefinition, newCRD *apiextensionsv1.CustomResourceDefinition) error {
13341311
logrus.Debugf("Comparing %#v to %#v", oldCRD.Spec.Versions, newCRD.Spec.Versions)
@@ -1364,6 +1341,36 @@ func validateV1CRDCompatibility(dynamicClient dynamic.Interface, oldCRD *apiexte
13641341
return nil
13651342
}
13661343

1344+
// Validate all existing served versions against new CRD's validation (if changed)
1345+
func validateV1Beta1CRDCompatibility(dynamicClient dynamic.Interface, oldCRD *apiextensionsv1beta1.CustomResourceDefinition, newCRD *apiextensionsv1beta1.CustomResourceDefinition) error {
1346+
logrus.Debugf("Comparing %#v to %#v", oldCRD.Spec.Validation, newCRD.Spec.Validation)
1347+
1348+
// TODO return early of all versions are equal
1349+
convertedCRD := &apiextensions.CustomResourceDefinition{}
1350+
if err := apiextensionsv1beta1.Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(newCRD, convertedCRD, nil); err != nil {
1351+
return err
1352+
}
1353+
for _, version := range oldCRD.Spec.Versions {
1354+
if !version.Served {
1355+
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: version.Name, Resource: oldCRD.Spec.Names.Plural}
1356+
err := validateExistingCRs(dynamicClient, gvr, convertedCRD)
1357+
if err != nil {
1358+
return err
1359+
}
1360+
}
1361+
}
1362+
1363+
if oldCRD.Spec.Version != "" {
1364+
gvr := schema.GroupVersionResource{Group: oldCRD.Spec.Group, Version: oldCRD.Spec.Version, Resource: oldCRD.Spec.Names.Plural}
1365+
err := validateExistingCRs(dynamicClient, gvr, convertedCRD)
1366+
if err != nil {
1367+
return err
1368+
}
1369+
}
1370+
logrus.Debugf("Successfully validated CRD %s\n", newCRD.Name)
1371+
return nil
1372+
}
1373+
13671374
func validateExistingCRs(dynamicClient dynamic.Interface, gvr schema.GroupVersionResource, newCRD *apiextensions.CustomResourceDefinition) error {
13681375
// make dynamic client
13691376
crList, err := dynamicClient.Resource(gvr).List(context.TODO(), metav1.ListOptions{})
@@ -1383,33 +1390,6 @@ func validateExistingCRs(dynamicClient dynamic.Interface, gvr schema.GroupVersio
13831390
return nil
13841391
}
13851392

1386-
// Attempt to remove stored versions that have been deprecated before allowing
1387-
// those versions to be removed from the new CRD.
1388-
// The function may not always succeed as storedVersions requires at least one
1389-
// version. If there is only stored version, it won't be removed until a new
1390-
// stored version is added.
1391-
func removeDeprecatedV1StoredVersions(oldCRD *apiextensionsv1.CustomResourceDefinition, newCRD *apiextensionsv1.CustomResourceDefinition) []string {
1392-
// StoredVersions requires to have at least one version.
1393-
if len(oldCRD.Status.StoredVersions) <= 1 {
1394-
return nil
1395-
}
1396-
1397-
newStoredVersions := []string{}
1398-
newCRDVersions := GetCRDV1VersionsMap(newCRD)
1399-
for _, v := range oldCRD.Status.StoredVersions {
1400-
_, ok := newCRDVersions[v]
1401-
if ok {
1402-
newStoredVersions = append(newStoredVersions, v)
1403-
}
1404-
}
1405-
1406-
if len(newStoredVersions) < 1 {
1407-
return nil
1408-
} else {
1409-
return newStoredVersions
1410-
}
1411-
}
1412-
14131393
// ExecutePlan applies a planned InstallPlan to a namespace.
14141394
func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
14151395
if plan.Status.Phase != v1alpha1.InstallPlanPhaseInstalling {
@@ -1436,7 +1416,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
14361416
}
14371417

14381418
ensurer := newStepEnsurer(kubeclient, crclient, dynamicClient)
1439-
b := newBuilder(kubeclient, dynamicClient, o.csvProvidedAPIsIndexer)
1419+
b := newBuilder(kubeclient, dynamicClient, o.csvProvidedAPIsIndexer, o.logger)
14401420

14411421
for i, step := range plan.Status.Plan {
14421422
doStep := true

pkg/controller/operators/catalog/operator_test.go

Lines changed: 0 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -135,203 +135,6 @@ func TestTransitionInstallPlan(t *testing.T) {
135135
}
136136
}
137137

138-
func TestEnsureV1CRDVersions(t *testing.T) {
139-
mainCRDPlural := "ins-main-abcde"
140-
141-
currentVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
142-
{
143-
Name: "v1alpha1",
144-
Served: true,
145-
Storage: true,
146-
},
147-
}
148-
149-
addedVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
150-
{
151-
Name: "v1alpha1",
152-
Served: true,
153-
Storage: false,
154-
},
155-
{
156-
Name: "v1alpha2",
157-
Served: true,
158-
Storage: true,
159-
},
160-
}
161-
162-
missingVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
163-
{
164-
Name: "v1alpha2",
165-
Served: true,
166-
Storage: true,
167-
},
168-
}
169-
170-
tests := []struct {
171-
name string
172-
oldCRD apiextensionsv1.CustomResourceDefinition
173-
newCRD apiextensionsv1.CustomResourceDefinition
174-
expectedFailure bool
175-
}{
176-
{
177-
name: "existing versions are present",
178-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
179-
oldCRD := v1crd(mainCRDPlural)
180-
oldCRD.Spec.Versions = currentVersions
181-
return oldCRD
182-
}(),
183-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
184-
newCRD := v1crd(mainCRDPlural)
185-
newCRD.Spec.Versions = addedVersions
186-
return newCRD
187-
}(),
188-
expectedFailure: false,
189-
},
190-
{
191-
name: "missing versions in new CRD 1",
192-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
193-
oldCRD := v1crd(mainCRDPlural)
194-
oldCRD.Spec.Versions = currentVersions
195-
return oldCRD
196-
}(),
197-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
198-
newCRD := v1crd(mainCRDPlural)
199-
newCRD.Spec.Versions = missingVersions
200-
return newCRD
201-
}(),
202-
expectedFailure: true,
203-
},
204-
{
205-
name: "missing version in new CRD 2",
206-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
207-
oldCRD := v1crd(mainCRDPlural)
208-
oldCRD.Spec.Versions[0].Name = "v1alpha1"
209-
return oldCRD
210-
}(),
211-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
212-
newCRD := v1crd(mainCRDPlural)
213-
newCRD.Spec.Versions[0].Name = "v1alpha2"
214-
return newCRD
215-
}(),
216-
expectedFailure: true,
217-
},
218-
{
219-
name: "existing version is present in new CRD's versions",
220-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
221-
oldCRD := v1crd(mainCRDPlural)
222-
oldCRD.Spec.Versions[0].Name = "v1alpha1"
223-
return oldCRD
224-
}(),
225-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
226-
newCRD := v1crd(mainCRDPlural)
227-
newCRD.Spec.Versions = addedVersions
228-
return newCRD
229-
}(),
230-
expectedFailure: false,
231-
},
232-
}
233-
234-
for _, tt := range tests {
235-
err := EnsureCRDVersions(&tt.oldCRD, &tt.newCRD)
236-
if tt.expectedFailure {
237-
require.Error(t, err)
238-
}
239-
}
240-
}
241-
242-
func TestRemoveDeprecatedV1StoredVersions(t *testing.T) {
243-
mainCRDPlural := "ins-main-test"
244-
245-
currentVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
246-
{
247-
Name: "v1alpha1",
248-
Served: true,
249-
Storage: false,
250-
},
251-
{
252-
Name: "v1alpha2",
253-
Served: true,
254-
Storage: true,
255-
},
256-
}
257-
258-
newVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
259-
{
260-
Name: "v1alpha2",
261-
Served: true,
262-
Storage: false,
263-
},
264-
{
265-
Name: "apiextensionsv1beta1",
266-
Served: true,
267-
Storage: true,
268-
},
269-
}
270-
271-
crdStatusStoredVersions := apiextensionsv1.CustomResourceDefinitionStatus{
272-
StoredVersions: []string{},
273-
}
274-
275-
tests := []struct {
276-
name string
277-
oldCRD apiextensionsv1.CustomResourceDefinition
278-
newCRD apiextensionsv1.CustomResourceDefinition
279-
expectedResult []string
280-
}{
281-
{
282-
name: "only one stored version exists",
283-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
284-
oldCRD := v1crd(mainCRDPlural)
285-
oldCRD.Spec.Versions = currentVersions
286-
oldCRD.Status = crdStatusStoredVersions
287-
oldCRD.Status.StoredVersions = []string{"v1alpha1"}
288-
return oldCRD
289-
}(),
290-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
291-
newCRD := v1crd(mainCRDPlural)
292-
newCRD.Spec.Versions = newVersions
293-
return newCRD
294-
}(),
295-
expectedResult: nil,
296-
},
297-
{
298-
name: "multiple stored versions with one deprecated version",
299-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
300-
oldCRD := v1crd(mainCRDPlural)
301-
oldCRD.Spec.Versions = currentVersions
302-
oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha2"}
303-
return oldCRD
304-
}(),
305-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
306-
newCRD := v1crd(mainCRDPlural)
307-
newCRD.Spec.Versions = newVersions
308-
return newCRD
309-
}(),
310-
expectedResult: []string{"v1alpha2"},
311-
},
312-
{
313-
name: "multiple stored versions with all deprecated version",
314-
oldCRD: func() apiextensionsv1.CustomResourceDefinition {
315-
oldCRD := v1crd(mainCRDPlural)
316-
oldCRD.Spec.Versions = currentVersions
317-
oldCRD.Status.StoredVersions = []string{"v1alpha1", "v1alpha3"}
318-
return oldCRD
319-
}(),
320-
newCRD: func() apiextensionsv1.CustomResourceDefinition {
321-
newCRD := v1crd(mainCRDPlural)
322-
newCRD.Spec.Versions = newVersions
323-
return newCRD
324-
}(),
325-
expectedResult: nil,
326-
},
327-
}
328-
329-
for _, tt := range tests {
330-
resultCRD := removeDeprecatedV1StoredVersions(&tt.oldCRD, &tt.newCRD)
331-
require.Equal(t, tt.expectedResult, resultCRD)
332-
}
333-
}
334-
335138
func TestExecutePlan(t *testing.T) {
336139
namespace := "ns"
337140

0 commit comments

Comments
 (0)