1
16
17 package diff
18
19 import (
20 "context"
21 "fmt"
22
23 corev1 "k8s.io/api/core/v1"
24 "k8s.io/apimachinery/pkg/api/meta"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/types"
28 "k8s.io/apimachinery/pkg/util/sets"
29 "k8s.io/cli-runtime/pkg/resource"
30 "k8s.io/client-go/dynamic"
31 "k8s.io/kubectl/pkg/util/prune"
32 )
33
34 type tracker struct {
35 visitedUids sets.Set[types.UID]
36 visitedNamespaces sets.Set[string]
37 }
38
39 func newTracker() *tracker {
40 return &tracker{
41 visitedUids: sets.New[types.UID](),
42 visitedNamespaces: sets.New[string](),
43 }
44 }
45
46 type pruner struct {
47 mapper meta.RESTMapper
48 dynamicClient dynamic.Interface
49
50 labelSelector string
51 resources []prune.Resource
52 }
53
54 func newPruner(dc dynamic.Interface, m meta.RESTMapper, r []prune.Resource, selector string) *pruner {
55 return &pruner{
56 dynamicClient: dc,
57 mapper: m,
58 resources: r,
59 labelSelector: selector,
60 }
61 }
62
63 func (p *pruner) pruneAll(tracker *tracker, namespaceSpecified bool) ([]runtime.Object, error) {
64 var allPruned []runtime.Object
65 namespacedRESTMappings, nonNamespacedRESTMappings, err := prune.GetRESTMappings(p.mapper, p.resources, namespaceSpecified)
66 if err != nil {
67 return allPruned, fmt.Errorf("error retrieving RESTMappings to prune: %v", err)
68 }
69
70 for n := range tracker.visitedNamespaces {
71 for _, m := range namespacedRESTMappings {
72 if pobjs, err := p.prune(tracker, n, m); err != nil {
73 return pobjs, fmt.Errorf("error pruning namespaced object %v: %v", m.GroupVersionKind, err)
74 } else {
75 allPruned = append(allPruned, pobjs...)
76 }
77 }
78 }
79 for _, m := range nonNamespacedRESTMappings {
80 if pobjs, err := p.prune(tracker, metav1.NamespaceNone, m); err != nil {
81 return allPruned, fmt.Errorf("error pruning nonNamespaced object %v: %v", m.GroupVersionKind, err)
82 } else {
83 allPruned = append(allPruned, pobjs...)
84 }
85 }
86
87 return allPruned, nil
88 }
89
90 func (p *pruner) prune(tracker *tracker, namespace string, mapping *meta.RESTMapping) ([]runtime.Object, error) {
91 objList, err := p.dynamicClient.Resource(mapping.Resource).
92 Namespace(namespace).
93 List(context.TODO(), metav1.ListOptions{
94 LabelSelector: p.labelSelector,
95 })
96 if err != nil {
97 return nil, err
98 }
99
100 objs, err := meta.ExtractList(objList)
101 if err != nil {
102 return nil, err
103 }
104
105 var pobjs []runtime.Object
106 for _, obj := range objs {
107 metadata, err := meta.Accessor(obj)
108 if err != nil {
109 return pobjs, err
110 }
111 annots := metadata.GetAnnotations()
112 if _, ok := annots[corev1.LastAppliedConfigAnnotation]; !ok {
113 continue
114 }
115 uid := metadata.GetUID()
116 if tracker.visitedUids.Has(uid) {
117 continue
118 }
119
120 pobjs = append(pobjs, obj)
121 }
122 return pobjs, nil
123 }
124
125
126 func (t *tracker) MarkVisited(info *resource.Info) {
127 if info.Namespaced() {
128 t.visitedNamespaces.Insert(info.Namespace)
129 }
130
131 metadata, err := meta.Accessor(info.Object)
132 if err != nil {
133 return
134 }
135 t.visitedUids.Insert(metadata.GetUID())
136 }
137
View as plain text