@@ -9,23 +9,21 @@ import (
99 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1010 apierrors "k8s.io/apimachinery/pkg/api/errors"
1111 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12- "k8s.io/apimachinery/pkg/runtime/schema"
1312 "k8s.io/apimachinery/pkg/types"
1413 "sigs.k8s.io/controller-runtime/pkg/client"
1514
15+ "github.com/operator-framework/kubectl-operator/internal/pkg/operand"
1616 "github.com/operator-framework/kubectl-operator/pkg/action"
1717)
1818
1919type OperatorUninstall struct {
2020 config * action.Configuration
2121
2222 Package string
23- DeleteAll bool
24- DeleteCRDs bool
23+ OperandStrategy operand.DeletionStrategy
2524 DeleteOperatorGroups bool
2625 DeleteOperatorGroupNames []string
27-
28- Logf func (string , ... interface {})
26+ Logf func (string , ... interface {})
2927}
3028
3129func NewOperatorUninstall (cfg * action.Configuration ) * OperatorUninstall {
@@ -44,11 +42,6 @@ func (e ErrPackageNotFound) Error() string {
4442}
4543
4644func (u * OperatorUninstall ) Run (ctx context.Context ) error {
47- if u .DeleteAll {
48- u .DeleteCRDs = true
49- u .DeleteOperatorGroups = true
50- }
51-
5245 subs := v1alpha1.SubscriptionList {}
5346 if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
5447 return fmt .Errorf ("list subscriptions: %v" , err )
@@ -69,62 +62,56 @@ func (u *OperatorUninstall) Run(ctx context.Context) error {
6962 csv , csvName , err := u .getSubscriptionCSV (ctx , sub )
7063 if err != nil && ! apierrors .IsNotFound (err ) {
7164 if csvName == "" {
72- return fmt .Errorf ("get subscription CSV : %v" , err )
65+ return fmt .Errorf ("get subscription csv : %v" , err )
7366 }
74- return fmt .Errorf ("get subscription CSV %q: %v" , csvName , err )
67+ return fmt .Errorf ("get subscription csv %q: %v" , csvName , err )
7568 }
7669
77- // Deletion order:
78- //
79- // 1. Subscription to prevent further installs or upgrades of the operator while cleaning up.
80- // 2. CustomResourceDefinitions so the operator has a chance to handle CRs that have finalizers.
81- // 3. ClusterServiceVersion. OLM puts an ownerref on every namespaced resource to the CSV,
82- // and an owner label on every cluster scoped resource so they get gc'd on deletion.
70+ // find operands related to the operator on cluster
71+ lister := action .NewOperatorListOperands (u .config )
72+ operands , err := lister .Run (ctx , u .Package )
73+ if err != nil {
74+ return fmt .Errorf ("list operands for operator %q: %v" , u .Package , err )
75+ }
76+ // validate the provided deletion strategy before proceeding to deletion
77+ if err := u .validStrategy (operands ); err != nil {
78+ return fmt .Errorf ("could not proceed with deletion of %q: %s" , u .Package , err )
79+ }
80+
81+ /*
82+ 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
92+ */
8393
8494 // Subscriptions can be deleted asynchronously.
8595 if err := u .deleteObjects (ctx , sub ); err != nil {
8696 return err
8797 }
8898
89- if csv != nil {
90- // Ensure CustomResourceDefinitions are deleted next, so that the operator
91- // has a chance to handle CRs that have finalizers.
92- if u .DeleteCRDs {
93- crds := getCRDs (csv )
94- if err := u .deleteObjects (ctx , crds ... ); err != nil {
95- return err
96- }
97- }
98-
99- // OLM puts an ownerref on every namespaced resource to the CSV,
100- // and an owner label on every cluster scoped resource. When CSV is deleted
101- // kube and olm gc will remove all the referenced resources.
102- if err := u .deleteObjects (ctx , csv ); err != nil {
99+ // If we could not find a csv associated with the subscription, that likely
100+ // means there is no CSV associated with it yet. Delete non-CSV related items only like the operatorgroup.
101+ if csv == nil {
102+ u .Logf ("csv for package %q not found" , u .Package )
103+ } else {
104+ if err := u .deleteCSVRelatedResources (ctx , csv , operands ); err != nil {
103105 return err
104106 }
105107 }
106108
107109 if u .DeleteOperatorGroups {
108- subs := v1alpha1.SubscriptionList {}
109- if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
110- return fmt .Errorf ("list subscriptions: %v" , err )
111- }
112- // If there are no subscriptions left, delete the operator group(s).
113- if len (subs .Items ) == 0 {
114- ogs := v1.OperatorGroupList {}
115- if err := u .config .Client .List (ctx , & ogs , client .InNamespace (u .config .Namespace )); err != nil {
116- return fmt .Errorf ("list operatorgroups: %v" , err )
117- }
118- for _ , og := range ogs .Items {
119- og := og
120- if len (u .DeleteOperatorGroupNames ) == 0 || contains (u .DeleteOperatorGroupNames , og .GetName ()) {
121- if err := u .deleteObjects (ctx , & og ); err != nil {
122- return err
123- }
124- }
125- }
110+ if err := u .deleteOperatorGroup (ctx ); err != nil {
111+ return fmt .Errorf ("delete operatorgroup: %v" , err )
126112 }
127113 }
114+
128115 return nil
129116}
130117
@@ -165,28 +152,73 @@ func (u *OperatorUninstall) getSubscriptionCSV(ctx context.Context, subscription
165152 return csv , name , nil
166153}
167154
168- func csvNameFromSubscription (subscription * v1alpha1.Subscription ) string {
169- if subscription .Status .InstalledCSV != "" {
170- return subscription .Status .InstalledCSV
155+ func (u * OperatorUninstall ) deleteOperatorGroup (ctx context.Context ) error {
156+ subs := v1alpha1.SubscriptionList {}
157+ if err := u .config .Client .List (ctx , & subs , client .InNamespace (u .config .Namespace )); err != nil {
158+ return fmt .Errorf ("list subscriptions: %v" , err )
171159 }
172- return subscription .Status .CurrentCSV
160+
161+ // If there are no subscriptions left, delete the operator group(s).
162+ if len (subs .Items ) == 0 {
163+ ogs := v1.OperatorGroupList {}
164+ if err := u .config .Client .List (ctx , & ogs , client .InNamespace (u .config .Namespace )); err != nil {
165+ return fmt .Errorf ("list operatorgroups: %v" , err )
166+ }
167+ for _ , og := range ogs .Items {
168+ og := og
169+ if len (u .DeleteOperatorGroupNames ) == 0 || contains (u .DeleteOperatorGroupNames , og .GetName ()) {
170+ if err := u .deleteObjects (ctx , & og ); err != nil {
171+ return err
172+ }
173+ }
174+ }
175+ }
176+ return nil
173177}
174178
175- // getCRDs returns the list of CRDs required by a CSV.
176- func getCRDs (csv * v1alpha1.ClusterServiceVersion ) (crds []client.Object ) {
177- for _ , resource := range csv .Status .RequirementStatus {
178- if resource .Kind == crdKind {
179- obj := & unstructured.Unstructured {}
180- obj .SetGroupVersionKind (schema.GroupVersionKind {
181- Group : resource .Group ,
182- Version : resource .Version ,
183- Kind : resource .Kind ,
184- })
185- obj .SetName (resource .Name )
186- crds = append (crds , obj )
179+ // 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+ 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 )
186+ }
187+ return nil
188+ }
189+
190+ func (u * OperatorUninstall ) deleteCSVRelatedResources (ctx context.Context , csv * v1alpha1.ClusterServiceVersion , operands * unstructured.UnstructuredList ) error {
191+ switch u .OperandStrategy .Kind {
192+ case operand .Ignore :
193+ for _ , op := range operands .Items {
194+ u .Logf ("%s %q orphaned" , strings .ToLower (op .GetKind ()), prettyPrint (op ))
195+ }
196+ case operand .Delete :
197+ for _ , op := range operands .Items {
198+ op := op
199+ if err := u .deleteObjects (ctx , & op ); err != nil {
200+ return err
201+ }
187202 }
203+ default :
204+ return fmt .Errorf ("unknown operand deletion strategy %q" , u .OperandStrategy )
205+ }
206+
207+ // OLM puts an ownerref on every namespaced resource to the CSV,
208+ // and an owner label on every cluster scoped resource. When CSV is deleted
209+ // kube and olm gc will remove all the referenced resources.
210+ if err := u .deleteObjects (ctx , csv ); err != nil {
211+ return err
188212 }
189- return
213+
214+ return nil
215+ }
216+
217+ func csvNameFromSubscription (subscription * v1alpha1.Subscription ) string {
218+ if subscription .Status .InstalledCSV != "" {
219+ return subscription .Status .InstalledCSV
220+ }
221+ return subscription .Status .CurrentCSV
190222}
191223
192224func contains (haystack []string , needle string ) bool {
@@ -197,3 +229,11 @@ func contains(haystack []string, needle string) bool {
197229 }
198230 return false
199231}
232+
233+ func prettyPrint (op unstructured.Unstructured ) string {
234+ namespaced := op .GetNamespace () != ""
235+ if namespaced {
236+ return fmt .Sprint (op .GetName () + "/" + op .GetNamespace ())
237+ }
238+ return op .GetName ()
239+ }
0 commit comments