1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package provider
17
18 import (
19 "context"
20 "reflect"
21 "strconv"
22
23 lru "github.com/hashicorp/golang-lru/v2"
24 "k8s.io/apimachinery/pkg/api/meta"
25 "k8s.io/apimachinery/pkg/runtime"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "k8s.io/apimachinery/pkg/types"
28 "sigs.k8s.io/controller-runtime/pkg/client"
29 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
30 )
31
32 var _ client.Client = &UpdatedClient{}
33
34 type UpdatedClient struct {
35 client client.Client
36 scheme *runtime.Scheme
37
38 cache *lru.Cache[string, runtime.Object]
39 }
40
41 func (c *UpdatedClient) objectKey(key client.ObjectKey, obj client.Object) (string, error) {
42 gvk, err := apiutil.GVKForObject(obj, c.scheme)
43 if err != nil {
44 return "", err
45 }
46
47 return gvk.String() + "/" + key.String(), nil
48 }
49
50 func (c *UpdatedClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
51 err := c.client.Get(ctx, key, obj)
52 if err != nil {
53 return err
54 }
55
56 objectKey, err := c.objectKey(key, obj)
57 if err != nil {
58 return err
59 }
60
61 cachedObject, ok := c.cache.Get(objectKey)
62 if ok {
63 cachedMeta, err := meta.Accessor(cachedObject)
64 if err != nil {
65 return nil
66 }
67
68 objMeta, err := meta.Accessor(obj)
69 if err != nil {
70 return nil
71 }
72
73 cachedResourceVersion, err := strconv.Atoi(cachedMeta.GetResourceVersion())
74 if err != nil {
75 return nil
76 }
77 newResourceVersion, err := strconv.Atoi(objMeta.GetResourceVersion())
78 if err != nil {
79 return nil
80 }
81 if cachedResourceVersion >= newResourceVersion {
82 cachedObject := cachedObject.(runtime.Object).DeepCopyObject()
83
84 reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(cachedObject).Elem())
85 }
86 }
87
88 return nil
89 }
90
91 func (c *UpdatedClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
92 return c.client.List(ctx, list, opts...)
93 }
94
95 func (c *UpdatedClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
96 return c.client.Create(ctx, obj, opts...)
97 }
98
99 func (c *UpdatedClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
100 return c.client.Delete(ctx, obj, opts...)
101 }
102
103 func (c *UpdatedClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
104 err := c.client.Update(ctx, obj, opts...)
105 if err != nil {
106 return err
107 }
108
109 err = c.writeCache(obj)
110 if err != nil {
111 return err
112 }
113
114 return nil
115 }
116
117 func (c *UpdatedClient) writeCache(obj client.Object) error {
118 objMeta, err := meta.Accessor(obj)
119 if err != nil {
120 return nil
121 }
122
123 objectKey, err := c.objectKey(types.NamespacedName{
124 Namespace: objMeta.GetNamespace(),
125 Name: objMeta.GetName(),
126 }, obj)
127 if err != nil {
128 return err
129 }
130
131 c.cache.Add(objectKey, obj.DeepCopyObject())
132
133 return nil
134 }
135
136 func (c *UpdatedClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
137 return c.client.Patch(ctx, obj, patch, opts...)
138 }
139
140 func (c *UpdatedClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
141 return c.client.DeleteAllOf(ctx, obj, opts...)
142 }
143
144 func (c *UpdatedClient) Scheme() *runtime.Scheme {
145 return c.client.Scheme()
146 }
147
148 func (c *UpdatedClient) RESTMapper() meta.RESTMapper {
149 return c.client.RESTMapper()
150 }
151
152 func (c *UpdatedClient) SubResource(subResource string) client.SubResourceClient {
153 return c.client.SubResource(subResource)
154 }
155
156 func (c *UpdatedClient) Status() client.StatusWriter {
157 return &UpdatedStatusWriter{
158 statusWriter: c.client.Status(),
159 client: c,
160 }
161 }
162
163 func (c *UpdatedClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
164 return c.client.GroupVersionKindFor(obj)
165 }
166
167 func (c *UpdatedClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
168 return c.client.IsObjectNamespaced(obj)
169 }
170
171 type UpdatedStatusWriter struct {
172 statusWriter client.StatusWriter
173 client *UpdatedClient
174 }
175
176 func (c *UpdatedStatusWriter) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
177 return c.statusWriter.Create(ctx, obj, subResource, opts...)
178 }
179
180 func (c *UpdatedStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
181 err := c.statusWriter.Update(ctx, obj, opts...)
182 if err != nil {
183 return err
184 }
185
186 err = c.client.writeCache(obj)
187 if err != nil {
188 return err
189 }
190
191 return nil
192 }
193
194 func (c *UpdatedStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
195 return c.statusWriter.Patch(ctx, obj, patch, opts...)
196 }
197