@@ -17,15 +17,17 @@ package operator
17
17
import (
18
18
"context"
19
19
"fmt"
20
- "sort"
21
20
"strings"
21
+ "time"
22
22
23
23
v1 "github.com/operator-framework/api/pkg/operators/v1"
24
24
"github.com/operator-framework/api/pkg/operators/v1alpha1"
25
25
apierrors "k8s.io/apimachinery/pkg/api/errors"
26
26
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27
27
"k8s.io/apimachinery/pkg/runtime/schema"
28
28
"k8s.io/apimachinery/pkg/types"
29
+ "k8s.io/apimachinery/pkg/util/wait"
30
+ "k8s.io/kubectl/pkg/util/slice"
29
31
"sigs.k8s.io/controller-runtime/pkg/client"
30
32
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
31
33
"sigs.k8s.io/yaml"
@@ -34,7 +36,11 @@ import (
34
36
type Uninstall struct {
35
37
config * Configuration
36
38
37
- Package string
39
+ Package string
40
+ DeleteAll bool
41
+ DeleteCRDs bool
42
+ DeleteOperatorGroups bool
43
+ DeleteOperatorGroupNames []string
38
44
}
39
45
40
46
func NewUninstall (cfg * Configuration ) * Uninstall {
@@ -44,6 +50,11 @@ func NewUninstall(cfg *Configuration) *Uninstall {
44
50
}
45
51
46
52
func (u * Uninstall ) Run (ctx context.Context ) error {
53
+ if u .DeleteAll {
54
+ u .DeleteCRDs = true
55
+ u .DeleteOperatorGroups = true
56
+ }
57
+
47
58
subs := v1alpha1.SubscriptionList {}
48
59
if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
49
60
return fmt .Errorf ("list subscriptions: %v" , err )
@@ -69,82 +80,115 @@ func (u *Uninstall) Run(ctx context.Context) error {
69
80
if err := u .config .Client .Get (ctx , catsrcKey , catsrc ); err != nil {
70
81
return fmt .Errorf ("get catalog source: %v" , err )
71
82
}
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 ))
77
84
78
85
// Since the install plan is owned by the subscription, we need to
79
86
// read all of the resource references from the install plan before
80
87
// 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
+ }
84
99
}
85
100
86
101
// Delete the subscription first, so that no further installs or upgrades
87
102
// 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 {
101
111
return err
102
112
}
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
106
119
}
107
120
108
121
// Delete the catalog source. This assumes that all underlying resources related
109
122
// to this catalog source have an owner reference to this catalog source so that
110
123
// 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
113
126
}
114
- u .config .Log ("catalogsource %q deleted\n " , catsrc .Name )
115
127
116
128
// If this was the last subscription in the namespace and the operator group is
117
129
// 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 )
122
133
}
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
+ }
127
145
}
128
- u .config .Log ("operatorgroup %q deleted\n " , og .Name )
129
146
}
130
147
}
131
148
}
149
+ return nil
150
+ }
132
151
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
+ }
133
178
return nil
134
179
}
135
180
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 ) {
137
182
installPlan := & v1alpha1.InstallPlan {}
138
183
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 )
140
185
}
141
186
142
- var objs []controllerutil.Object
143
187
for _ , step := range installPlan .Status .Plan {
144
- obj := & unstructured.Unstructured {Object : map [string ]interface {}{}}
145
188
lowerKind := strings .ToLower (step .Resource .Kind )
189
+ obj := & unstructured.Unstructured {Object : map [string ]interface {}{}}
146
190
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 )
148
192
}
149
193
obj .SetGroupVersionKind (schema.GroupVersionKind {
150
194
Group : step .Resource .Group ,
@@ -159,7 +203,19 @@ func (u *Uninstall) getInstallPlanResources(ctx context.Context, installPlanKey
159
203
if step .Resource .Kind == "ServiceAccount" && obj .GetNamespace () == "" {
160
204
obj .SetNamespace (installPlanKey .Namespace )
161
205
}
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
+ }
163
219
}
164
- return objs , nil
220
+ return crds , csvs , others , nil
165
221
}
0 commit comments