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