@@ -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