Skip to content

Commit 4739c1f

Browse files
authored
wait for CRD deletion; delete all objects created by install plan (#3664)
1 parent a8f5e00 commit 4739c1f

File tree

3 files changed

+139
-66
lines changed

3 files changed

+139
-66
lines changed

internal/cmd/operator-sdk/cleanup/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ func NewCmd() *cobra.Command {
3939
Run: func(cmd *cobra.Command, args []string) {
4040
u := operator.NewUninstall(cfg)
4141
u.Package = args[0]
42+
u.DeleteAll = true
43+
u.DeleteOperatorGroupNames = []string{operator.SDKOperatorGroupName}
4244

4345
ctx, cancel := context.WithTimeout(cmd.Context(), timeout)
4446
defer cancel()

internal/operator/uninstall.go

Lines changed: 101 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ package operator
1717
import (
1818
"context"
1919
"fmt"
20-
"sort"
2120
"strings"
21+
"time"
2222

2323
v1 "github.com/operator-framework/api/pkg/operators/v1"
2424
"github.com/operator-framework/api/pkg/operators/v1alpha1"
2525
apierrors "k8s.io/apimachinery/pkg/api/errors"
2626
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2727
"k8s.io/apimachinery/pkg/runtime/schema"
2828
"k8s.io/apimachinery/pkg/types"
29+
"k8s.io/apimachinery/pkg/util/wait"
30+
"k8s.io/kubectl/pkg/util/slice"
2931
"sigs.k8s.io/controller-runtime/pkg/client"
3032
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3133
"sigs.k8s.io/yaml"
@@ -34,7 +36,11 @@ import (
3436
type Uninstall struct {
3537
config *Configuration
3638

37-
Package string
39+
Package string
40+
DeleteAll bool
41+
DeleteCRDs bool
42+
DeleteOperatorGroups bool
43+
DeleteOperatorGroupNames []string
3844
}
3945

4046
func NewUninstall(cfg *Configuration) *Uninstall {
@@ -44,6 +50,11 @@ func NewUninstall(cfg *Configuration) *Uninstall {
4450
}
4551

4652
func (u *Uninstall) Run(ctx context.Context) error {
53+
if u.DeleteAll {
54+
u.DeleteCRDs = true
55+
u.DeleteOperatorGroups = true
56+
}
57+
4758
subs := v1alpha1.SubscriptionList{}
4859
if err := u.config.Client.List(ctx, &subs, client.InNamespace(u.config.Namespace)); err != nil {
4960
return fmt.Errorf("list subscriptions: %v", err)
@@ -69,82 +80,115 @@ func (u *Uninstall) Run(ctx context.Context) error {
6980
if err := u.config.Client.Get(ctx, catsrcKey, catsrc); err != nil {
7081
return fmt.Errorf("get catalog source: %v", err)
7182
}
72-
73-
installPlanKey := types.NamespacedName{
74-
Namespace: sub.Status.InstallPlanRef.Namespace,
75-
Name: sub.Status.InstallPlanRef.Name,
76-
}
83+
catsrc.SetGroupVersionKind(v1alpha1.SchemeGroupVersion.WithKind(v1alpha1.CatalogSourceKind))
7784

7885
// Since the install plan is owned by the subscription, we need to
7986
// read all of the resource references from the install plan before
8087
// deleting the subscription.
81-
deleteObjs, err := u.getInstallPlanResources(ctx, installPlanKey)
82-
if err != nil {
83-
return err
88+
var crds, csvs, others []controllerutil.Object
89+
if sub.Status.InstallPlanRef != nil {
90+
ipKey := types.NamespacedName{
91+
Namespace: sub.Status.InstallPlanRef.Namespace,
92+
Name: sub.Status.InstallPlanRef.Name,
93+
}
94+
var err error
95+
crds, csvs, others, err = u.getInstallPlanResources(ctx, ipKey)
96+
if err != nil {
97+
return fmt.Errorf("get install plan resources: %v", err)
98+
}
8499
}
85100

86101
// Delete the subscription first, so that no further installs or upgrades
87102
// of the operator occur while we're cleaning up.
88-
if err := u.config.Client.Delete(ctx, sub); err != nil {
89-
return fmt.Errorf("delete subscription %q: %v", sub.Name, err)
90-
}
91-
u.config.Log("subscription %q deleted\n", sub.Name)
92-
93-
// Ensure CustomResourceDefinitions are deleted first, so that the operator
94-
// has a chance to handle CRs that have finalizers.
95-
sort.SliceStable(deleteObjs, func(i, j int) bool {
96-
return deleteObjs[i].GetObjectKind().GroupVersionKind().Kind == "CustomResourceDefinition"
97-
})
98-
for _, obj := range deleteObjs {
99-
err := u.config.Client.Delete(ctx, obj)
100-
if err != nil && !apierrors.IsNotFound(err) {
103+
if err := u.deleteObjects(ctx, false, sub); err != nil {
104+
return err
105+
}
106+
107+
if u.DeleteCRDs {
108+
// Ensure CustomResourceDefinitions are deleted next, so that the operator
109+
// has a chance to handle CRs that have finalizers.
110+
if err := u.deleteObjects(ctx, true, crds...); err != nil {
101111
return err
102112
}
103-
if err == nil {
104-
u.config.Log("%s %q deleted\n", strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind), obj.GetName())
105-
}
113+
}
114+
115+
// Delete CSVs and all other objects created by the install plan.
116+
objects := append(csvs, others...)
117+
if err := u.deleteObjects(ctx, true, objects...); err != nil {
118+
return err
106119
}
107120

108121
// Delete the catalog source. This assumes that all underlying resources related
109122
// to this catalog source have an owner reference to this catalog source so that
110123
// they are automatically garbage-collected.
111-
if err := u.config.Client.Delete(ctx, catsrc); err != nil {
112-
return fmt.Errorf("delete catalog source: %v", err)
124+
if err := u.deleteObjects(ctx, true, catsrc); err != nil {
125+
return err
113126
}
114-
u.config.Log("catalogsource %q deleted\n", catsrc.Name)
115127

116128
// If this was the last subscription in the namespace and the operator group is
117129
// the one we created, delete it
118-
if len(subs.Items) == 1 {
119-
ogs := v1.OperatorGroupList{}
120-
if err := u.config.Client.List(ctx, &ogs, client.InNamespace(u.config.Namespace)); err != nil {
121-
return fmt.Errorf("list operatorgroups: %v", err)
130+
if u.DeleteOperatorGroups {
131+
if err := u.config.Client.List(ctx, &subs, client.InNamespace(u.config.Namespace)); err != nil {
132+
return fmt.Errorf("list subscriptions: %v", err)
122133
}
123-
for _, og := range ogs.Items {
124-
if og.GetName() == SDKOperatorGroupName {
125-
if err := u.config.Client.Delete(ctx, &og); err != nil {
126-
return fmt.Errorf("delete operatorgroup %q: %v", og.Name, err)
134+
if len(subs.Items) == 0 {
135+
ogs := v1.OperatorGroupList{}
136+
if err := u.config.Client.List(ctx, &ogs, client.InNamespace(u.config.Namespace)); err != nil {
137+
return fmt.Errorf("list operatorgroups: %v", err)
138+
}
139+
for _, og := range ogs.Items {
140+
og := og
141+
if len(u.DeleteOperatorGroupNames) == 0 || slice.ContainsString(u.DeleteOperatorGroupNames, og.GetName(), nil) {
142+
if err := u.deleteObjects(ctx, false, &og); err != nil {
143+
return err
144+
}
127145
}
128-
u.config.Log("operatorgroup %q deleted\n", og.Name)
129146
}
130147
}
131148
}
149+
return nil
150+
}
132151

152+
func (u *Uninstall) deleteObjects(ctx context.Context, waitForDelete bool, objs ...controllerutil.Object) error {
153+
for _, obj := range objs {
154+
obj := obj
155+
lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
156+
if err := u.config.Client.Delete(ctx, obj); err != nil && !apierrors.IsNotFound(err) {
157+
return fmt.Errorf("delete %s %q: %v", lowerKind, obj.GetName(), err)
158+
} else if err == nil {
159+
u.config.Log("%s %q deleted", lowerKind, obj.GetName())
160+
}
161+
if waitForDelete {
162+
key, err := client.ObjectKeyFromObject(obj)
163+
if err != nil {
164+
return fmt.Errorf("get %s key: %v", lowerKind, err)
165+
}
166+
if err := wait.PollImmediateUntil(250*time.Millisecond, func() (bool, error) {
167+
if err := u.config.Client.Get(ctx, key, obj); apierrors.IsNotFound(err) {
168+
return true, nil
169+
} else if err != nil {
170+
return false, err
171+
}
172+
return false, nil
173+
}, ctx.Done()); err != nil {
174+
return fmt.Errorf("wait for %s deleted: %v", lowerKind, err)
175+
}
176+
}
177+
}
133178
return nil
134179
}
135180

136-
func (u *Uninstall) getInstallPlanResources(ctx context.Context, installPlanKey types.NamespacedName) ([]controllerutil.Object, error) {
181+
func (u *Uninstall) getInstallPlanResources(ctx context.Context, installPlanKey types.NamespacedName) (crds, csvs, others []controllerutil.Object, err error) {
137182
installPlan := &v1alpha1.InstallPlan{}
138183
if err := u.config.Client.Get(ctx, installPlanKey, installPlan); err != nil {
139-
return nil, fmt.Errorf("get install plan: %v", err)
184+
return nil, nil, nil, fmt.Errorf("get install plan: %v", err)
140185
}
141186

142-
var objs []controllerutil.Object
143187
for _, step := range installPlan.Status.Plan {
144-
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
145188
lowerKind := strings.ToLower(step.Resource.Kind)
189+
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
146190
if err := yaml.Unmarshal([]byte(step.Resource.Manifest), &obj.Object); err != nil {
147-
return nil, fmt.Errorf("parse %s manifest %q: %v", lowerKind, step.Resource.Name, err)
191+
return nil, nil, nil, fmt.Errorf("parse %s manifest %q: %v", lowerKind, step.Resource.Name, err)
148192
}
149193
obj.SetGroupVersionKind(schema.GroupVersionKind{
150194
Group: step.Resource.Group,
@@ -159,7 +203,19 @@ func (u *Uninstall) getInstallPlanResources(ctx context.Context, installPlanKey
159203
if step.Resource.Kind == "ServiceAccount" && obj.GetNamespace() == "" {
160204
obj.SetNamespace(installPlanKey.Namespace)
161205
}
162-
objs = append(objs, obj)
206+
switch step.Resource.Kind {
207+
case "CustomResourceDefinition":
208+
crds = append(crds, obj)
209+
case "ClusterServiceVersion":
210+
csvs = append(csvs, obj)
211+
default:
212+
// Skip non-CRD/non-CSV resources in the install plan that were not created by the install plan.
213+
// This means we avoid deleting things like the default service account.
214+
if step.Status != v1alpha1.StepStatusCreated {
215+
continue
216+
}
217+
others = append(others, obj)
218+
}
163219
}
164-
return objs, nil
220+
return crds, csvs, others, nil
165221
}

test/integration/operator_olm_test.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ import (
2424

2525
apimanifests "github.com/operator-framework/api/pkg/manifests"
2626
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
27+
"github.com/sirupsen/logrus"
2728
"github.com/stretchr/testify/assert"
29+
corev1 "k8s.io/api/core/v1"
2830
apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
31+
"k8s.io/apimachinery/pkg/util/wait"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
2933

3034
operator "github.com/operator-framework/operator-sdk/internal/olm/operator"
3135
operator2 "github.com/operator-framework/operator-sdk/internal/operator"
@@ -106,12 +110,8 @@ func PackageManifestsAllNamespaces(t *testing.T) {
106110
Version: defaultOperatorVersion,
107111
}
108112
// Cleanup.
109-
cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath}
110-
assert.NoError(t, cfg.Load())
111-
uninstall := operator2.NewUninstall(cfg)
112-
uninstall.Package = defaultOperatorName
113113
defer func() {
114-
if err := doUninstall(uninstall, opcmd.Timeout); err != nil {
114+
if err := doUninstall(t, kubeconfigPath, opcmd.Timeout); err != nil {
115115
t.Fatal(err)
116116
}
117117
}()
@@ -169,24 +169,19 @@ func PackageManifestsBasic(t *testing.T) {
169169
ManifestsDir: manifestsDir,
170170
Version: defaultOperatorVersion,
171171
}
172-
// Cleanup.
173-
cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath}
174-
assert.NoError(t, cfg.Load())
175-
uninstall := operator2.NewUninstall(cfg)
176-
uninstall.Package = defaultOperatorName
177172

178173
// "Remove operator before deploy"
179-
assert.Error(t, doUninstall(uninstall, opcmd.Timeout))
174+
assert.Error(t, doUninstall(t, kubeconfigPath, opcmd.Timeout))
180175

181176
// "Deploy operator"
182177
assert.NoError(t, opcmd.Run())
183178
// "Fail to deploy operator after deploy"
184179
assert.Error(t, opcmd.Run())
185180

186181
// "Remove operator after deploy"
187-
assert.NoError(t, doUninstall(uninstall, opcmd.Timeout))
182+
assert.NoError(t, doUninstall(t, kubeconfigPath, opcmd.Timeout))
188183
// "Remove operator after removal"
189-
assert.Error(t, doUninstall(uninstall, opcmd.Timeout))
184+
assert.Error(t, doUninstall(t, kubeconfigPath, opcmd.Timeout))
190185
}
191186

192187
func PackageManifestsMultiplePackages(t *testing.T) {
@@ -269,20 +264,40 @@ func PackageManifestsMultiplePackages(t *testing.T) {
269264
ManifestsDir: manifestsDir,
270265
Version: operatorVersion2,
271266
}
272-
// Cleanup.
273-
cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath}
274-
assert.NoError(t, cfg.Load())
275-
uninstall := operator2.NewUninstall(cfg)
276-
uninstall.Package = defaultOperatorName
277267

278268
// "Deploy operator"
279269
assert.NoError(t, opcmd.Run())
280270
// "Remove operator after deploy"
281-
assert.NoError(t, doUninstall(uninstall, opcmd.Timeout))
271+
assert.NoError(t, doUninstall(t, kubeconfigPath, opcmd.Timeout))
282272
}
283273

284-
func doUninstall(u *operator2.Uninstall, timeout time.Duration) error {
274+
func doUninstall(t *testing.T, kubeconfigPath string, timeout time.Duration) error {
275+
cfg := &operator2.Configuration{KubeconfigPath: kubeconfigPath}
276+
cfg.Log = logrus.Infof
277+
assert.NoError(t, cfg.Load())
278+
uninstall := operator2.NewUninstall(cfg)
279+
uninstall.DeleteAll = true
280+
uninstall.DeleteOperatorGroupNames = []string{operator2.SDKOperatorGroupName}
281+
uninstall.Package = defaultOperatorName
282+
285283
ctx, cancel := context.WithTimeout(context.Background(), timeout)
286284
defer cancel()
287-
return u.Run(ctx)
285+
if err := uninstall.Run(ctx); err != nil {
286+
return err
287+
}
288+
return waitForPackageManifestConfigMapDeletion(ctx, cfg, defaultOperatorName)
289+
}
290+
291+
func waitForPackageManifestConfigMapDeletion(ctx context.Context, cfg *operator2.Configuration, packageName string) error {
292+
cfgmaps := corev1.ConfigMapList{}
293+
opts := []client.ListOption{
294+
client.InNamespace(cfg.Namespace),
295+
client.MatchingLabels{"owner": "operator-sdk", "package-name": packageName},
296+
}
297+
return wait.PollImmediateUntil(250*time.Millisecond, func() (bool, error) {
298+
if err := cfg.Client.List(ctx, &cfgmaps, opts...); err != nil {
299+
return false, err
300+
}
301+
return len(cfgmaps.Items) == 0, nil
302+
}, ctx.Done())
288303
}

0 commit comments

Comments
 (0)