@@ -4,12 +4,14 @@ import (
44 "context"
55 "fmt"
66 "strings"
7+ "time"
78
89 v1 "github.com/operator-framework/api/pkg/operators/v1"
910 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1011 apierrors "k8s.io/apimachinery/pkg/api/errors"
1112 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1213 "k8s.io/apimachinery/pkg/types"
14+ "k8s.io/apimachinery/pkg/util/wait"
1315 "sigs.k8s.io/controller-runtime/pkg/client"
1416
1517 "github.com/operator-framework/kubectl-operator/internal/pkg/operand"
@@ -21,15 +23,18 @@ type OperatorUninstall struct {
2123
2224 Package string
2325 OperandStrategy operand.DeletionStrategy
26+ DeleteAll bool
27+ DeleteOperator bool
2428 DeleteOperatorGroups bool
2529 DeleteOperatorGroupNames []string
2630 Logf func (string , ... interface {})
2731}
2832
2933func NewOperatorUninstall (cfg * action.Configuration ) * OperatorUninstall {
3034 return & OperatorUninstall {
31- config : cfg ,
32- Logf : func (string , ... interface {}) {},
35+ config : cfg ,
36+ OperandStrategy : operand .Abort ,
37+ Logf : func (string , ... interface {}) {},
3338 }
3439}
3540
@@ -42,6 +47,18 @@ func (e ErrPackageNotFound) Error() string {
4247}
4348
4449func (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+
4562 subs := v1alpha1.SubscriptionList {}
4663 if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
4764 return fmt .Errorf ("list subscriptions: %v" , err )
@@ -75,20 +92,23 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
7592 }
7693 // validate the provided deletion strategy before proceeding to deletion
7794 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 )
7996 }
8097
8198 /*
8299 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
92112 */
93113
94114 // Subscriptions can be deleted asynchronously.
@@ -106,6 +126,12 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
106126 }
107127 }
108128
129+ if u .DeleteOperator {
130+ if err := u .deleteOperator (ctx ); err != nil {
131+ return fmt .Errorf ("delete operator: %v" , err )
132+ }
133+ }
134+
109135 if u .DeleteOperatorGroups {
110136 if err := u .deleteOperatorGroup (ctx ); err != nil {
111137 return fmt .Errorf ("delete operatorgroup: %v" , err )
@@ -115,6 +141,10 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
115141 return nil
116142}
117143
144+ func (u * OperatorUninstall ) operatorName () string {
145+ return fmt .Sprintf ("%s.%s" , u .Package , u .config .Namespace )
146+ }
147+
118148func (u * OperatorUninstall ) deleteObjects (ctx context.Context , objs ... client.Object ) error {
119149 for _ , obj := range objs {
120150 obj := obj
@@ -152,6 +182,64 @@ func (u *OperatorUninstall) getSubscriptionCSV(ctx context.Context, subscription
152182 return csv , name , nil
153183}
154184
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+
155243func (u * OperatorUninstall ) deleteOperatorGroup (ctx context.Context ) error {
156244 subs := v1alpha1.SubscriptionList {}
157245 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 {
177265}
178266
179267// 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
181268func (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
186271 }
187272 return nil
188273}
189274
190275func (u * OperatorUninstall ) deleteCSVRelatedResources (ctx context.Context , csv * v1alpha1.ClusterServiceVersion , operands * unstructured.UnstructuredList ) error {
191- switch u .OperandStrategy . Kind {
276+ switch u .OperandStrategy {
192277 case operand .Ignore :
193278 for _ , op := range operands .Items {
194279 u .Logf ("%s %q orphaned" , strings .ToLower (op .GetKind ()), prettyPrint (op ))
@@ -197,18 +282,16 @@ func (u *OperatorUninstall) deleteCSVRelatedResources(ctx context.Context, csv *
197282 for _ , op := range operands .Items {
198283 op := op
199284 if err := u .deleteObjects (ctx , & op ); err != nil {
200- return err
285+ return fmt . Errorf ( "delete operand: %v" , err )
201286 }
202287 }
203- default :
204- return fmt .Errorf ("unknown operand deletion strategy %q" , u .OperandStrategy )
205288 }
206289
207290 // OLM puts an ownerref on every namespaced resource to the CSV,
208291 // and an owner label on every cluster scoped resource. When CSV is deleted
209292 // kube and olm gc will remove all the referenced resources.
210293 if err := u .deleteObjects (ctx , csv ); err != nil {
211- return err
294+ return fmt . Errorf ( "delete csv: %v" , err )
212295 }
213296
214297 return nil
0 commit comments