1
2
3
4
5
6
7
8
9
10
11
12 package prune
13
14 import (
15 "context"
16 "errors"
17
18 apierrors "k8s.io/apimachinery/pkg/api/errors"
19 "k8s.io/apimachinery/pkg/api/meta"
20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
22 "k8s.io/client-go/dynamic"
23 "k8s.io/klog/v2"
24 "k8s.io/kubectl/pkg/cmd/util"
25 "sigs.k8s.io/cli-utils/pkg/apply/filter"
26 "sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
27 "sigs.k8s.io/cli-utils/pkg/common"
28 "sigs.k8s.io/cli-utils/pkg/inventory"
29 "sigs.k8s.io/cli-utils/pkg/object"
30 )
31
32
33
34 type Pruner struct {
35 InvClient inventory.Client
36 Client dynamic.Interface
37 Mapper meta.RESTMapper
38 }
39
40
41
42 func NewPruner(factory util.Factory, invClient inventory.Client) (*Pruner, error) {
43
44 client, err := factory.DynamicClient()
45 if err != nil {
46 return nil, err
47 }
48 mapper, err := factory.ToRESTMapper()
49 if err != nil {
50 return nil, err
51 }
52 return &Pruner{
53 InvClient: invClient,
54 Client: client,
55 Mapper: mapper,
56 }, nil
57 }
58
59
60
61 type Options struct {
62
63
64 DryRunStrategy common.DryRunStrategy
65
66 PropagationPolicy metav1.DeletionPropagation
67
68
69
70 Destroy bool
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 func (p *Pruner) Prune(
90 objs object.UnstructuredSet,
91 pruneFilters []filter.ValidationFilter,
92 taskContext *taskrunner.TaskContext,
93 taskName string,
94 opts Options,
95 ) error {
96 eventFactory := CreateEventFactory(opts.Destroy, taskName)
97
98
99 for _, obj := range objs {
100 id := object.UnstructuredToObjMetadata(obj)
101 klog.V(5).Infof("evaluating prune filters (object: %q)", id)
102
103
104 uid := obj.GetUID()
105 if uid == "" {
106 err := object.NotFound([]interface{}{"metadata", "uid"}, "")
107 if klog.V(4).Enabled() {
108
109 klog.Errorf("prune uid lookup errored (object: %s): %v", id, err)
110 }
111 taskContext.SendEvent(eventFactory.CreateFailedEvent(id, err))
112 taskContext.InventoryManager().AddFailedDelete(id)
113 continue
114 }
115
116
117 var filterErr error
118 for _, pruneFilter := range pruneFilters {
119 klog.V(6).Infof("prune filter evaluating (filter: %s, object: %s)", pruneFilter.Name(), id)
120 filterErr = pruneFilter.Filter(obj)
121 if filterErr != nil {
122 var fatalErr *filter.FatalError
123 if errors.As(filterErr, &fatalErr) {
124 if klog.V(4).Enabled() {
125
126 klog.Errorf("prune filter errored (filter: %s, object: %s): %v", pruneFilter.Name(), id, fatalErr.Err)
127 }
128 taskContext.SendEvent(eventFactory.CreateFailedEvent(id, fatalErr.Err))
129 taskContext.InventoryManager().AddFailedDelete(id)
130 break
131 }
132 klog.V(4).Infof("prune filtered (filter: %s, object: %s): %v", pruneFilter.Name(), id, filterErr)
133
134
135
136 var abandonErr *filter.AnnotationPreventedDeletionError
137 if errors.As(filterErr, &abandonErr) {
138 if !opts.DryRunStrategy.ClientOrServerDryRun() {
139 var err error
140 obj, err = p.removeInventoryAnnotation(obj)
141 if err != nil {
142 if klog.V(4).Enabled() {
143
144 klog.Errorf("error removing annotation (object: %q, annotation: %q): %v", id, inventory.OwningInventoryKey, err)
145 }
146 taskContext.SendEvent(eventFactory.CreateFailedEvent(id, err))
147 taskContext.InventoryManager().AddFailedDelete(id)
148 break
149 }
150
151
152 taskContext.AddAbandonedObject(id)
153 }
154 }
155
156 taskContext.SendEvent(eventFactory.CreateSkippedEvent(obj, filterErr))
157 taskContext.InventoryManager().AddSkippedDelete(id)
158 break
159 }
160 }
161 if filterErr != nil {
162 continue
163 }
164
165
166 if !opts.DryRunStrategy.ClientOrServerDryRun() {
167 klog.V(4).Infof("deleting object (object: %q)", id)
168 err := p.deleteObject(id, metav1.DeleteOptions{
169
170
171 Preconditions: &metav1.Preconditions{
172 UID: &uid,
173 },
174 PropagationPolicy: &opts.PropagationPolicy,
175 })
176 if err != nil {
177 if apierrors.IsNotFound(err) {
178 klog.Warningf("error deleting object (object: %q): object not found: object may have been deleted asynchronously by another client", id)
179
180 } else {
181 if klog.V(4).Enabled() {
182
183 klog.Errorf("error deleting object (object: %q): %v", id, err)
184 }
185 taskContext.SendEvent(eventFactory.CreateFailedEvent(id, err))
186 taskContext.InventoryManager().AddFailedDelete(id)
187 continue
188 }
189 }
190 }
191 taskContext.InventoryManager().AddSuccessfulDelete(id, obj.GetUID())
192 taskContext.SendEvent(eventFactory.CreateSuccessEvent(obj))
193 }
194 return nil
195 }
196
197
198 func (p *Pruner) removeInventoryAnnotation(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
199
200
201 obj = obj.DeepCopy()
202 id := object.UnstructuredToObjMetadata(obj)
203 annotations := obj.GetAnnotations()
204 if annotations != nil {
205 if _, ok := annotations[inventory.OwningInventoryKey]; ok {
206 klog.V(4).Infof("removing annotation (object: %q, annotation: %q)", id, inventory.OwningInventoryKey)
207 delete(annotations, inventory.OwningInventoryKey)
208 obj.SetAnnotations(annotations)
209 namespacedClient, err := p.namespacedClient(id)
210 if err != nil {
211 return obj, err
212 }
213 _, err = namespacedClient.Update(context.TODO(), obj, metav1.UpdateOptions{})
214 return obj, err
215 }
216 }
217 return obj, nil
218 }
219
220
221
222
223
224 func (p *Pruner) GetPruneObjs(
225 inv inventory.Info,
226 objs object.UnstructuredSet,
227 opts Options,
228 ) (object.UnstructuredSet, error) {
229 ids := object.UnstructuredSetToObjMetadataSet(objs)
230 invIDs, err := p.InvClient.GetClusterObjs(inv)
231 if err != nil {
232 return nil, err
233 }
234
235 ids = invIDs.Diff(ids)
236 objs = object.UnstructuredSet{}
237 for _, id := range ids {
238 pruneObj, err := p.getObject(id)
239 if err != nil {
240 if meta.IsNoMatchError(err) {
241 klog.V(4).Infof("skip pruning (object: %q): resource type not registered", id)
242 continue
243 }
244 if apierrors.IsNotFound(err) {
245 klog.V(4).Infof("skip pruning (object: %q): resource not found", id)
246 continue
247 }
248 return nil, err
249 }
250 objs = append(objs, pruneObj)
251 }
252 return objs, nil
253 }
254
255 func (p *Pruner) getObject(id object.ObjMetadata) (*unstructured.Unstructured, error) {
256 namespacedClient, err := p.namespacedClient(id)
257 if err != nil {
258 return nil, err
259 }
260 return namespacedClient.Get(context.TODO(), id.Name, metav1.GetOptions{})
261 }
262
263 func (p *Pruner) deleteObject(id object.ObjMetadata, opts metav1.DeleteOptions) error {
264 namespacedClient, err := p.namespacedClient(id)
265 if err != nil {
266 return err
267 }
268 return namespacedClient.Delete(context.TODO(), id.Name, opts)
269 }
270
271 func (p *Pruner) namespacedClient(id object.ObjMetadata) (dynamic.ResourceInterface, error) {
272 mapping, err := p.Mapper.RESTMapping(id.GroupKind)
273 if err != nil {
274 return nil, err
275 }
276 return p.Client.Resource(mapping.Resource).Namespace(id.Namespace), nil
277 }
278
View as plain text