@@ -3,14 +3,20 @@ package action
33import  (
44	"context" 
55	"fmt" 
6+ 	"strings" 
7+ 	"time" 
68
79	v1 "github.com/operator-framework/api/pkg/operators/v1" 
810	"github.com/operator-framework/api/pkg/operators/v1alpha1" 
911	"github.com/spf13/pflag" 
10- 	apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 
1112	apierrors "k8s.io/apimachinery/pkg/api/errors" 
13+ 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 
14+ 	"k8s.io/apimachinery/pkg/runtime/schema" 
1215	"k8s.io/apimachinery/pkg/types" 
16+ 	"k8s.io/apimachinery/pkg/util/wait" 
1317	"sigs.k8s.io/controller-runtime/pkg/client" 
18+ 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 
19+ 	"sigs.k8s.io/yaml" 
1420
1521	"github.com/operator-framework/kubectl-operator/internal/pkg/log" 
1622)
@@ -59,28 +65,44 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
5965		return  fmt .Errorf ("operator package %q not found" , u .Package )
6066	}
6167
68+ 	var  crds , csvs , others  []controllerutil.Object 
69+ 	if  sub .Status .InstallPlanRef  !=  nil  {
70+ 		ipKey  :=  types.NamespacedName {
71+ 			Namespace : sub .Status .InstallPlanRef .Namespace ,
72+ 			Name :      sub .Status .InstallPlanRef .Name ,
73+ 		}
74+ 		var  err  error 
75+ 		crds , csvs , others , err  =  u .getInstallPlanResources (ctx , ipKey )
76+ 		if  err  !=  nil  {
77+ 			return  fmt .Errorf ("get install plan resources: %v" , err )
78+ 		}
79+ 	}
80+ 
6281	if  err  :=  u .config .Client .Delete (ctx , sub ); err  !=  nil  {
6382		return  fmt .Errorf ("delete subscription %q: %v" , sub .Name , err )
6483	}
6584	log .Printf ("subscription %q deleted" , sub .Name )
6685
67- 	if  sub . Status . CurrentCSV   !=   ""   &&   sub . Status . CurrentCSV   !=   sub . Status . InstalledCSV  {
68- 		if  err  :=  u .deleteCSVandCRDs (ctx , sub . Status . CurrentCSV ,  true ); err  !=  nil  {
86+ 	if  u . DeleteCRDs  {
87+ 		if  err  :=  u .deleteCRDs (ctx , crds ); err  !=  nil  {
6988			return  err 
7089		}
7190	}
72- 	if  sub .Status .InstalledCSV  !=  ""  {
73- 		if  err  :=  u .deleteCSVandCRDs (ctx , sub .Status .InstalledCSV , false ); err  !=  nil  {
74- 			return  err 
75- 		}
91+ 
92+ 	if  err  :=  u .deleteObjects (ctx , false , csvs ); err  !=  nil  {
93+ 		return  err 
94+ 	}
95+ 
96+ 	if  err  :=  u .deleteObjects (ctx , false , others ); err  !=  nil  {
97+ 		return  err 
7698	}
7799
78100	if  u .DeleteOperatorGroup  {
79- 		csvs  :=  v1alpha1.ClusterServiceVersionList {}
80- 		if  err  :=  u .config .Client .List (ctx , & csvs , client .InNamespace (u .config .Namespace )); err  !=  nil  {
101+ 		subs  :=  v1alpha1.SubscriptionList {}
102+ 		if  err  :=  u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err  !=  nil  {
81103			return  fmt .Errorf ("list clusterserviceversions: %v" , err )
82104		}
83- 		if  len (csvs .Items ) ==  0  {
105+ 		if  len (subs .Items ) ==  0  {
84106			ogs  :=  v1.OperatorGroupList {}
85107			if  err  :=  u .config .Client .List (ctx , & ogs , client .InNamespace (u .config .Namespace )); err  !=  nil  {
86108				return  fmt .Errorf ("list operatorgroups: %v" , err )
@@ -98,34 +120,78 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
98120	return  nil 
99121}
100122
101- func  (u  * OperatorUninstall ) deleteCSVandCRDs (ctx  context.Context , csvName  string , ignoreNotFound  bool ) error  {
102- 	csvKey  :=  types.NamespacedName {
103- 		Name :      csvName ,
104- 		Namespace : u .config .Namespace ,
105- 	}
106- 	csv  :=  v1alpha1.ClusterServiceVersion {}
107- 	if  err  :=  u .config .Client .Get (ctx , csvKey , & csv ); err  !=  nil  {
108- 		if  ! apierrors .IsNotFound (err ) ||  ! ignoreNotFound  {
109- 			return  fmt .Errorf ("get clusterserviceversion %q: %v" , csvName , err )
110- 		}
123+ func  (u  * OperatorUninstall ) deleteCRDs (ctx  context.Context , crds  []controllerutil.Object ) error  {
124+ 	if  err  :=  u .deleteObjects (ctx , true , crds ); err  !=  nil  {
125+ 		return  err 
111126	}
127+ 	return  nil 
128+ }
112129
113- 	if  u .DeleteCRDs  {
114- 		ownedCRDs  :=  csv .Spec .CustomResourceDefinitions .Owned 
115- 		for  _ , ownedCRD  :=  range  ownedCRDs  {
116- 			crd  :=  apiextv1.CustomResourceDefinition {}
117- 			crd .SetName (ownedCRD .Name )
118- 			if  err  :=  u .config .Client .Delete (ctx , & crd ); err  !=  nil  {
119- 				return  fmt .Errorf ("delete crd %q: %v" , ownedCRD .Name , err )
130+ func  (u  * OperatorUninstall ) deleteObjects (ctx  context.Context , waitForDelete  bool , objs  []controllerutil.Object ) error  {
131+ 	for  _ , obj  :=  range  objs  {
132+ 		obj  :=  obj 
133+ 		lowerKind  :=  strings .ToLower (obj .GetObjectKind ().GroupVersionKind ().Kind )
134+ 		if  err  :=  u .config .Client .Delete (ctx , obj ); err  !=  nil  &&  ! apierrors .IsNotFound (err ) {
135+ 			return  fmt .Errorf ("delete %s %q: %v" , lowerKind , obj .GetName (), err )
136+ 		} else  if  err  ==  nil  {
137+ 			log .Printf ("%s %q deleted" , lowerKind , obj .GetName ())
138+ 		}
139+ 		if  waitForDelete  {
140+ 			key , err  :=  client .ObjectKeyFromObject (obj )
141+ 			if  err  !=  nil  {
142+ 				return  fmt .Errorf ("get %s key: %v" , lowerKind , err )
143+ 			}
144+ 			if  err  :=  wait .PollImmediateUntil (250 * time .Millisecond , func () (bool , error ) {
145+ 				if  err  :=  u .config .Client .Get (ctx , key , obj ); apierrors .IsNotFound (err ) {
146+ 					return  true , nil 
147+ 				} else  if  err  !=  nil  {
148+ 					return  false , err 
149+ 				}
150+ 				return  false , nil 
151+ 			}, ctx .Done ()); err  !=  nil  {
152+ 				return  fmt .Errorf ("wait for %s deleted: %v" , lowerKind , err )
120153			}
121- 			log .Printf ("crd %q deleted" , ownedCRD .Name )
122154		}
123155	}
156+ 	return  nil 
157+ }
124158
125- 	if  err  :=  u .config .Client .Delete (ctx , & csv ); err  !=  nil  {
126- 		return  fmt .Errorf ("delete csv %q: %v" , csvName , err )
159+ func  (u  * OperatorUninstall ) getInstallPlanResources (ctx  context.Context , installPlanKey  types.NamespacedName ) (crds , csvs , others  []controllerutil.Object , err  error ) {
160+ 	installPlan  :=  & v1alpha1.InstallPlan {}
161+ 	if  err  :=  u .config .Client .Get (ctx , installPlanKey , installPlan ); err  !=  nil  {
162+ 		return  nil , nil , nil , fmt .Errorf ("get install plan: %v" , err )
127163	}
128- 	log .Printf ("csv %q deleted" , csvName )
129164
130- 	return  nil 
165+ 	for  _ , step  :=  range  installPlan .Status .Plan  {
166+ 		if  step .Status  !=  v1alpha1 .StepStatusCreated  {
167+ 			continue 
168+ 		}
169+ 		obj  :=  & unstructured.Unstructured {Object : map [string ]interface {}{}}
170+ 		lowerKind  :=  strings .ToLower (step .Resource .Kind )
171+ 		if  err  :=  yaml .Unmarshal ([]byte (step .Resource .Manifest ), & obj .Object ); err  !=  nil  {
172+ 			return  nil , nil , nil , fmt .Errorf ("parse %s manifest %q: %v" , lowerKind , step .Resource .Name , err )
173+ 		}
174+ 		obj .SetGroupVersionKind (schema.GroupVersionKind {
175+ 			Group :   step .Resource .Group ,
176+ 			Version : step .Resource .Version ,
177+ 			Kind :    step .Resource .Kind ,
178+ 		})
179+ 
180+ 		// TODO(joelanford): This seems necessary for service accounts tied to 
181+ 		//    cluster roles and cluster role bindings because the SA namespace 
182+ 		//    is not set in the manifest in this case. 
183+ 		//    See: https://github.com/operator-framework/operator-lifecycle-manager/blob/c9405d035bc50d9aa290220cb8d75b0402e72707/pkg/controller/registry/resolver/rbac.go#L133 
184+ 		if  step .Resource .Kind  ==  "ServiceAccount"  &&  obj .GetNamespace () ==  ""  {
185+ 			obj .SetNamespace (installPlanKey .Namespace )
186+ 		}
187+ 		switch  step .Resource .Kind  {
188+ 		case  crdKind :
189+ 			crds  =  append (crds , obj )
190+ 		case  csvKind :
191+ 			csvs  =  append (csvs , obj )
192+ 		default :
193+ 			others  =  append (others , obj )
194+ 		}
195+ 	}
196+ 	return  crds , csvs , others , nil 
131197}
0 commit comments