@@ -4,12 +4,14 @@ import (
4
4
"context"
5
5
"fmt"
6
6
"strings"
7
+ "time"
7
8
8
9
v1 "github.com/operator-framework/api/pkg/operators/v1"
9
10
"github.com/operator-framework/api/pkg/operators/v1alpha1"
10
11
apierrors "k8s.io/apimachinery/pkg/api/errors"
11
12
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12
13
"k8s.io/apimachinery/pkg/types"
14
+ "k8s.io/apimachinery/pkg/util/wait"
13
15
"sigs.k8s.io/controller-runtime/pkg/client"
14
16
15
17
"github.com/operator-framework/kubectl-operator/internal/pkg/operand"
@@ -21,15 +23,18 @@ type OperatorUninstall struct {
21
23
22
24
Package string
23
25
OperandStrategy operand.DeletionStrategy
26
+ DeleteAll bool
27
+ DeleteOperator bool
24
28
DeleteOperatorGroups bool
25
29
DeleteOperatorGroupNames []string
26
30
Logf func (string , ... interface {})
27
31
}
28
32
29
33
func NewOperatorUninstall (cfg * action.Configuration ) * OperatorUninstall {
30
34
return & OperatorUninstall {
31
- config : cfg ,
32
- Logf : func (string , ... interface {}) {},
35
+ config : cfg ,
36
+ OperandStrategy : operand .Abort ,
37
+ Logf : func (string , ... interface {}) {},
33
38
}
34
39
}
35
40
@@ -42,6 +47,18 @@ func (e ErrPackageNotFound) Error() string {
42
47
}
43
48
44
49
func (u * OperatorUninstall ) Run (ctx context.Context ) error {
50
+ if u .DeleteAll {
51
+ u .DeleteOperator = true
52
+ u .DeleteOperatorGroups = true
53
+ }
54
+ if u .DeleteOperator {
55
+ u .OperandStrategy = operand .Delete
56
+ }
57
+
58
+ if err := u .OperandStrategy .Valid (); err != nil {
59
+ return err
60
+ }
61
+
45
62
subs := v1alpha1.SubscriptionList {}
46
63
if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
47
64
return fmt .Errorf ("list subscriptions: %v" , err )
@@ -75,20 +92,23 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
75
92
}
76
93
// validate the provided deletion strategy before proceeding to deletion
77
94
if err := u .validStrategy (operands ); err != nil {
78
- return fmt .Errorf ("could not proceed with deletion of %q: %s " , u .Package , err )
95
+ return fmt .Errorf ("could not proceed with deletion of %q: %w " , u .Package , err )
79
96
}
80
97
81
98
/*
82
99
Deletion order:
83
- 1. Subscription to prevent further installs or upgrades of the operator while cleaning up.
84
-
85
- If the CSV exists:
86
- 2. Operands so the operator has a chance to handle CRs that have finalizers.
87
- Note: the correct strategy must be chosen in order to process an opertor delete with operand on-cluster.
88
- 3. ClusterServiceVersion. OLM puts an ownerref on every namespaced resource to the CSV,
89
- and an owner label on every cluster scoped resource so they get gc'd on deletion.
90
-
91
- 4. OperatorGroup in the namespace if no other subscriptions are in that namespace and OperatorGroup deletion is specified
100
+ 1. Subscription to prevent further installs or upgrades of the operator while cleaning up.
101
+
102
+ If the CSV exists:
103
+ 2. Operands so the operator has a chance to handle CRs that have finalizers.
104
+ Note: the correct strategy must be chosen in order to process an opertor delete with operand
105
+ on-cluster.
106
+ 3. ClusterServiceVersion. OLM puts an ownerref on every namespaced resource to the CSV,
107
+ and an owner label on every cluster scoped resource so they get gc'd on deletion.
108
+
109
+ 4. The Operator and all objects referenced by it if Operator deletion is specified
110
+ 5. OperatorGroup in the namespace if no other subscriptions are in that namespace and OperatorGroup deletion
111
+ is specified
92
112
*/
93
113
94
114
// Subscriptions can be deleted asynchronously.
@@ -106,6 +126,12 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
106
126
}
107
127
}
108
128
129
+ if u .DeleteOperator {
130
+ if err := u .deleteOperator (ctx ); err != nil {
131
+ return fmt .Errorf ("delete operator: %v" , err )
132
+ }
133
+ }
134
+
109
135
if u .DeleteOperatorGroups {
110
136
if err := u .deleteOperatorGroup (ctx ); err != nil {
111
137
return fmt .Errorf ("delete operatorgroup: %v" , err )
@@ -115,6 +141,10 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
115
141
return nil
116
142
}
117
143
144
+ func (u * OperatorUninstall ) operatorName () string {
145
+ return fmt .Sprintf ("%s.%s" , u .Package , u .config .Namespace )
146
+ }
147
+
118
148
func (u * OperatorUninstall ) deleteObjects (ctx context.Context , objs ... client.Object ) error {
119
149
for _ , obj := range objs {
120
150
obj := obj
@@ -152,6 +182,64 @@ func (u *OperatorUninstall) getSubscriptionCSV(ctx context.Context, subscription
152
182
return csv , name , nil
153
183
}
154
184
185
+ // deleteOperator deletes the operator and everything it references. It:
186
+ // - gets the operator object so that we can look up its references
187
+ // - deletes the references
188
+ // - waits until the operator object references are all deleted (this step is
189
+ // necessary because OLM recreates the operator object until no other
190
+ // referenced objects exist)
191
+ // - deletes the operator
192
+ func (u * OperatorUninstall ) deleteOperator (ctx context.Context ) error {
193
+ // get the operator
194
+ var op v1.Operator
195
+ key := types.NamespacedName {Name : u .operatorName ()}
196
+ if err := u .config .Client .Get (ctx , key , & op ); err != nil {
197
+ if apierrors .IsNotFound (err ) {
198
+ return nil
199
+ }
200
+ return fmt .Errorf ("get operator: %w" , err )
201
+ }
202
+
203
+ // build objects for each of the references and then delete them
204
+ objs := []client.Object {}
205
+ for _ , ref := range op .Status .Components .Refs {
206
+ obj := unstructured.Unstructured {}
207
+ obj .SetName (ref .Name )
208
+ obj .SetNamespace (ref .Namespace )
209
+ obj .SetGroupVersionKind (ref .GroupVersionKind ())
210
+ objs = append (objs , & obj )
211
+ }
212
+ if err := u .deleteObjects (ctx , objs ... ); err != nil {
213
+ return fmt .Errorf ("delete operator references: %v" , err )
214
+ }
215
+
216
+ // wait until all of the objects we just deleted disappear from the
217
+ // operator's references.
218
+ if err := wait .PollImmediateUntil (time .Millisecond * 100 , func () (bool , error ) {
219
+ var check v1.Operator
220
+ if err := u .config .Client .Get (ctx , key , & check ); err != nil {
221
+ if apierrors .IsNotFound (err ) {
222
+ return true , nil
223
+ }
224
+ return false , fmt .Errorf ("get operator: %w" , err )
225
+ }
226
+ if check .Status .Components == nil || len (check .Status .Components .Refs ) == 0 {
227
+ return true , nil
228
+ }
229
+ return false , nil
230
+ }, ctx .Done ()); err != nil {
231
+ return err
232
+ }
233
+
234
+ // delete the operator
235
+ op .SetGroupVersionKind (v1 .GroupVersion .WithKind ("Operator" ))
236
+ if err := u .deleteObjects (ctx , & op ); err != nil {
237
+ return fmt .Errorf ("delete operator: %v" , err )
238
+ }
239
+
240
+ return nil
241
+ }
242
+
155
243
func (u * OperatorUninstall ) deleteOperatorGroup (ctx context.Context ) error {
156
244
subs := v1alpha1.SubscriptionList {}
157
245
if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
@@ -177,18 +265,15 @@ func (u *OperatorUninstall) deleteOperatorGroup(ctx context.Context) error {
177
265
}
178
266
179
267
// validStrategy validates the deletion strategy against the operands on-cluster
180
- // TODO define and use an OperandStrategyError that the cmd can use errors.As() on to provide external callers a more generic error
181
268
func (u * OperatorUninstall ) validStrategy (operands * unstructured.UnstructuredList ) error {
182
- if len (operands .Items ) > 0 && u .OperandStrategy .Kind == operand .Cancel {
183
- return fmt .Errorf ("%d operands exist and operand strategy %q is in use: " +
184
- "delete operands manually or re-run uninstall with a different operand deletion strategy." +
185
- "\n \n See kubectl operator uninstall --help for more information on operand deletion strategies." , len (operands .Items ), operand .Cancel )
269
+ if len (operands .Items ) > 0 && u .OperandStrategy == operand .Abort {
270
+ return operand .ErrAbortStrategy
186
271
}
187
272
return nil
188
273
}
189
274
190
275
func (u * OperatorUninstall ) deleteCSVRelatedResources (ctx context.Context , csv * v1alpha1.ClusterServiceVersion , operands * unstructured.UnstructuredList ) error {
191
- switch u .OperandStrategy . Kind {
276
+ switch u .OperandStrategy {
192
277
case operand .Ignore :
193
278
for _ , op := range operands .Items {
194
279
u .Logf ("%s %q orphaned" , strings .ToLower (op .GetKind ()), prettyPrint (op ))
@@ -197,18 +282,16 @@ func (u *OperatorUninstall) deleteCSVRelatedResources(ctx context.Context, csv *
197
282
for _ , op := range operands .Items {
198
283
op := op
199
284
if err := u .deleteObjects (ctx , & op ); err != nil {
200
- return err
285
+ return fmt . Errorf ( "delete operand: %v" , err )
201
286
}
202
287
}
203
- default :
204
- return fmt .Errorf ("unknown operand deletion strategy %q" , u .OperandStrategy )
205
288
}
206
289
207
290
// OLM puts an ownerref on every namespaced resource to the CSV,
208
291
// and an owner label on every cluster scoped resource. When CSV is deleted
209
292
// kube and olm gc will remove all the referenced resources.
210
293
if err := u .deleteObjects (ctx , csv ); err != nil {
211
- return err
294
+ return fmt . Errorf ( "delete csv: %v" , err )
212
295
}
213
296
214
297
return nil
0 commit comments