...
1
16
17 package v1
18
19 import (
20 "fmt"
21 "sync"
22 "time"
23
24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
25 "k8s.io/apimachinery/pkg/runtime/schema"
26 "k8s.io/apimachinery/pkg/util/managedfields"
27 "k8s.io/client-go/discovery"
28 "k8s.io/kube-openapi/pkg/util/proto"
29 "sigs.k8s.io/structured-merge-diff/v4/typed"
30 )
31
32
33
34 const openAPISchemaTTL = time.Minute
35
36
37
38 type UnstructuredExtractor interface {
39 Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
40 ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error)
41 }
42
43
44
45 type gvkParserCache struct {
46
47
48 discoveryClient discovery.DiscoveryInterface
49
50 mu sync.Mutex
51
52 gvkParser *managedfields.GvkParser
53
54 lastChecked time.Time
55 }
56
57
58 func regenerateGVKParser(dc discovery.DiscoveryInterface) (*managedfields.GvkParser, error) {
59 doc, err := dc.OpenAPISchema()
60 if err != nil {
61 return nil, err
62 }
63
64 models, err := proto.NewOpenAPIData(doc)
65 if err != nil {
66 return nil, err
67 }
68
69 return managedfields.NewGVKParser(models, false)
70 }
71
72
73 func (c *gvkParserCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
74 c.mu.Lock()
75 defer c.mu.Unlock()
76
77
78 if time.Since(c.lastChecked) > openAPISchemaTTL {
79 c.lastChecked = time.Now()
80 parser, err := regenerateGVKParser(c.discoveryClient)
81 if err != nil {
82 return nil, err
83 }
84 c.gvkParser = parser
85 }
86 return c.gvkParser.Type(gvk), nil
87 }
88
89 type extractor struct {
90 cache *gvkParserCache
91 }
92
93
94
95 func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) (UnstructuredExtractor, error) {
96 parser, err := regenerateGVKParser(dc)
97 if err != nil {
98 return nil, fmt.Errorf("failed generating initial GVK Parser: %v", err)
99 }
100 return &extractor{
101 cache: &gvkParserCache{
102 gvkParser: parser,
103 discoveryClient: dc,
104 },
105 }, nil
106 }
107
108
109
110 func (e *extractor) Extract(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
111 return e.extractUnstructured(object, fieldManager, "")
112 }
113
114
115
116
117 func (e *extractor) ExtractStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) {
118 return e.extractUnstructured(object, fieldManager, "status")
119 }
120
121 func (e *extractor) extractUnstructured(object *unstructured.Unstructured, fieldManager string, subresource string) (*unstructured.Unstructured, error) {
122 gvk := object.GetObjectKind().GroupVersionKind()
123 objectType, err := e.cache.objectTypeForGVK(gvk)
124 if err != nil {
125 return nil, fmt.Errorf("failed to fetch the objectType: %v", err)
126 }
127 result := &unstructured.Unstructured{}
128 err = managedfields.ExtractInto(object, *objectType, fieldManager, result, subresource)
129 if err != nil {
130 return nil, fmt.Errorf("failed calling ExtractInto for unstructured: %v", err)
131 }
132 result.SetName(object.GetName())
133 result.SetNamespace(object.GetNamespace())
134 result.SetKind(object.GetKind())
135 result.SetAPIVersion(object.GetAPIVersion())
136 return result, nil
137 }
138
View as plain text