-
Notifications
You must be signed in to change notification settings - Fork 36
/
operator_uninstall.go
195 lines (169 loc) · 5.97 KB
/
operator_uninstall.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package action
import (
"context"
"fmt"
"strings"
v1 "github.com/operator-framework/api/pkg/operators/v1"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/operator-framework/kubectl-operator/internal/pkg/operand"
"github.com/operator-framework/kubectl-operator/pkg/action"
)
type OperatorUninstall struct {
config *action.Configuration
Package string
OperandStrategy operand.DeletionStrategy
DeleteOperatorGroups bool
DeleteOperatorGroupNames []string
Logf func(string, ...interface{})
}
func NewOperatorUninstall(cfg *action.Configuration) *OperatorUninstall {
return &OperatorUninstall{
config: cfg,
Logf: func(string, ...interface{}) {},
}
}
type ErrPackageNotFound struct {
PackageName string
}
func (e ErrPackageNotFound) Error() string {
return fmt.Sprintf("package %q not found", e.PackageName)
}
func (u *OperatorUninstall) Run(ctx context.Context) error {
subs := v1alpha1.SubscriptionList{}
if err := u.config.Client.List(ctx, &subs, client.InNamespace(u.config.Namespace)); err != nil {
return fmt.Errorf("list subscriptions: %v", err)
}
var sub *v1alpha1.Subscription
for _, s := range subs.Items {
s := s
if u.Package == s.Spec.Package {
sub = &s
break
}
}
if sub == nil {
return &ErrPackageNotFound{u.Package}
}
csv, csvName, err := u.getSubscriptionCSV(ctx, sub)
if err != nil && !apierrors.IsNotFound(err) {
if csvName == "" {
return fmt.Errorf("get subscription CSV: %v", err)
}
return fmt.Errorf("get subscription CSV %q: %v", csvName, err)
}
// Subscriptions can be deleted asynchronously.
if err := u.deleteObjects(ctx, sub); err != nil {
return err
}
// If we could not find a csv associated with the subscription, that likely
// means there is no CSV associated with it yet. Return a nil error.
if csv == nil {
u.Logf("csv for package %q not found", u.Package)
return nil
}
// Deletion order:
//
// 1. Subscription to prevent further installs or upgrades of the operator while cleaning up.
// 2. Operands so the operator has a chance to handle CRs that have finalizers.
// Note: the correct strategy must be chosen in order to process an opertor delete with operand on-cluster.
// 3. ClusterServiceVersion. OLM puts an ownerref on every namespaced resource to the CSV,
// and an owner label on every cluster scoped resource so they get gc'd on deletion.
// create lister and list operands
lister := action.NewOperatorListOperands(u.config)
operands, err := lister.Run(ctx, u.Package)
if err != nil {
return fmt.Errorf("listing operands associated with operator %s: %s", u.Package, err)
}
switch u.OperandStrategy.Kind {
case operand.Cancel:
if len(operands.Items) > 0 {
return fmt.Errorf("%d operands exist and operand strategy %q is in use; "+
"delete operands or re-run with a different operand strategy", len(operands.Items), operand.Cancel)
}
case operand.Ignore:
for _, op := range operands.Items {
u.Logf("%s %q orphaned", strings.ToLower(op.GetKind()), op.GetName()+"/"+op.GetNamespace())
}
case operand.Delete:
for _, op := range operands.Items {
op := op
if err := u.deleteObjects(ctx, &op); err != nil {
return err
}
u.Logf("%s %q deleted", strings.ToLower(op.GetKind()), op.GetName()+"/"+op.GetNamespace())
}
default:
return fmt.Errorf("unknown operand deletion strategy %q", u.OperandStrategy)
}
// OLM puts an ownerref on every namespaced resource to the CSV,
// and an owner label on every cluster scoped resource. When CSV is deleted
// kube and olm gc will remove all the referenced resources.
if err := u.deleteObjects(ctx, csv); err != nil {
return err
}
// If there are no subscriptions left, delete the operator group(s).
if len(subs.Items) == 0 {
ogs := v1.OperatorGroupList{}
if err := u.config.Client.List(ctx, &ogs, client.InNamespace(u.config.Namespace)); err != nil {
return fmt.Errorf("list operatorgroups: %v", err)
}
for _, og := range ogs.Items {
og := og
if len(u.DeleteOperatorGroupNames) == 0 || contains(u.DeleteOperatorGroupNames, og.GetName()) {
if err := u.deleteObjects(ctx, &og); err != nil {
return err
}
}
}
}
return nil
}
func (u *OperatorUninstall) deleteObjects(ctx context.Context, objs ...client.Object) error {
for _, obj := range objs {
obj := obj
lowerKind := strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind)
if err := u.config.Client.Delete(ctx, obj); err != nil && !apierrors.IsNotFound(err) {
return fmt.Errorf("delete %s %q: %v", lowerKind, obj.GetName(), err)
} else if err == nil {
u.Logf("%s %q deleted", lowerKind, obj.GetName())
}
}
return waitForDeletion(ctx, u.config.Client, objs...)
}
// getSubscriptionCSV looks up the installed CSV name from the provided subscription and fetches it.
func (u *OperatorUninstall) getSubscriptionCSV(ctx context.Context, subscription *v1alpha1.Subscription) (*v1alpha1.ClusterServiceVersion, string, error) {
name := csvNameFromSubscription(subscription)
// If we could not find a name in the subscription, that likely
// means there is no CSV associated with it yet. This should
// not be treated as an error, so return a nil CSV with a nil error.
if name == "" {
return nil, "", nil
}
key := types.NamespacedName{
Name: name,
Namespace: subscription.GetNamespace(),
}
csv := &v1alpha1.ClusterServiceVersion{}
if err := u.config.Client.Get(ctx, key, csv); err != nil {
return nil, name, err
}
csv.SetGroupVersionKind(v1alpha1.SchemeGroupVersion.WithKind(csvKind))
return csv, name, nil
}
func csvNameFromSubscription(subscription *v1alpha1.Subscription) string {
if subscription.Status.InstalledCSV != "" {
return subscription.Status.InstalledCSV
}
return subscription.Status.CurrentCSV
}
func contains(haystack []string, needle string) bool {
for _, n := range haystack {
if n == needle {
return true
}
}
return false
}