Skip to content

Commit b6b3cfe

Browse files
author
Shawn Hurley
committed
Adding two types of client for unstructured and typed
1 parent d8a7cfc commit b6b3cfe

File tree

8 files changed

+441
-158
lines changed

8 files changed

+441
-158
lines changed

pkg/cache/cache_test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,9 @@ var _ = Describe("Informer Cache", func() {
185185
By("listing pods in test-namespace-1")
186186
listObj := &kcorev1.PodList{}
187187
Expect(informerCache.List(context.Background(),
188+
listObj,
188189
client.InNamespace(testNamespaceOne),
189-
listObj)).To(Succeed())
190+
)).To(Succeed())
190191

191192
By("verifying that the returned pods are in test-namespace-1")
192193
Expect(listObj.Items).NotTo(BeEmpty())
@@ -231,7 +232,8 @@ var _ = Describe("Informer Cache", func() {
231232
Version: "v1",
232233
Kind: "ServiceList",
233234
})
234-
Expect(informerCache.List(context.Background(), nil, listObj)).To(Succeed())
235+
err := informerCache.List(context.Background(), listObj)
236+
Expect(err).To(Succeed())
235237

236238
By("verifying that the returned list contains the Kubernetes service")
237239
// NB: kubernetes default service is automatically created in testenv.
@@ -271,8 +273,9 @@ var _ = Describe("Informer Cache", func() {
271273
Version: "v1",
272274
Kind: "PodList",
273275
})
274-
Expect(informerCache.List(context.Background(), client.InNamespace(testNamespaceTwo).
275-
MatchingLabels(map[string]string{"test-label": "test-pod-2"}), &out)).To(Succeed())
276+
err := informerCache.List(context.Background(), &out, client.InNamespace(testNamespaceTwo),
277+
client.MatchingLabels(map[string]string{"test-label": "test-pod-2"}))
278+
Expect(err).To(Succeed())
276279

277280
By("verifying the returned pods have the correct label")
278281
Expect(out.Items).NotTo(BeEmpty())
@@ -294,8 +297,9 @@ var _ = Describe("Informer Cache", func() {
294297
Kind: "PodList",
295298
})
296299
labels := map[string]string{"test-label": "test-pod-2"}
297-
Expect(informerCache.List(context.Background(),
298-
client.MatchingLabels(labels), &out)).To(Succeed())
300+
err := informerCache.List(context.Background(),
301+
&out, client.MatchingLabels(labels))
302+
Expect(err).To(Succeed())
299303

300304
By("verifying multiple pods with the same label in different namespaces are returned")
301305
Expect(out.Items).NotTo(BeEmpty())
@@ -315,9 +319,8 @@ var _ = Describe("Informer Cache", func() {
315319
Version: "v1",
316320
Kind: "PodList",
317321
})
318-
Expect(informerCache.List(context.Background(),
319-
client.InNamespace(testNamespaceOne),
320-
listObj)).To(Succeed())
322+
err := informerCache.List(context.Background(), listObj, client.InNamespace(testNamespaceOne))
323+
Expect(err).To(Succeed())
321324

322325
By("verifying that the returned pods are in test-namespace-1")
323326
Expect(listObj.Items).NotTo(BeEmpty())
@@ -524,7 +527,7 @@ var _ = Describe("Informer Cache", func() {
524527
By("verifying the object is received on the channel")
525528
Eventually(out).Should(Receive(Equal(pod)))
526529
close(done)
527-
})
530+
}, 3)
528531

529532
It("should be able to index an object field then retrieve objects by that field", func() {
530533
By("creating the cache")
@@ -565,16 +568,15 @@ var _ = Describe("Informer Cache", func() {
565568
Version: "v1",
566569
Kind: "PodList",
567570
})
568-
Expect(informer.List(context.Background(),
569-
client.MatchingField("spec.restartPolicy", "OnFailure"),
570-
listObj)).To(Succeed())
571+
err = informer.List(context.Background(), listObj, client.MatchingField("spec.restartPolicy", "OnFailure"))
572+
Expect(err).To(Succeed())
571573

572574
By("verifying that the returned pods have correct restart policy")
573575
Expect(listObj.Items).NotTo(BeEmpty())
574576
Expect(listObj.Items).Should(HaveLen(1))
575577
actual := listObj.Items[0]
576578
Expect(actual.GetName()).To(Equal("test-pod-3"))
577-
})
579+
}, 3)
578580
})
579581
})
580582
})

pkg/cache/informer_cache.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,7 @@ func (ip *informerCache) Get(ctx context.Context, key client.ObjectKey, out runt
5858
}
5959

6060
// List implements Reader
61-
func (ip *informerCache) List(ctx context.Context, opts *client.ListOptions, out runtime.Object) error {
62-
itemsPtr, err := apimeta.GetItemsPtr(out)
63-
if err != nil {
64-
return nil
65-
}
66-
61+
func (ip *informerCache) List(ctx context.Context, out runtime.Object, opts ...client.ListOptionFunc) error {
6762
gvk, err := apiutil.GVKForObject(out, ip.Scheme)
6863
if err != nil {
6964
return err

pkg/client/client.go

Lines changed: 43 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ import (
2222
"reflect"
2323

2424
"k8s.io/apimachinery/pkg/api/meta"
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2526
"k8s.io/apimachinery/pkg/runtime"
26-
"k8s.io/apimachinery/pkg/runtime/schema"
2727
"k8s.io/apimachinery/pkg/runtime/serializer"
28+
"k8s.io/client-go/dynamic"
2829
"k8s.io/client-go/kubernetes/scheme"
2930
"k8s.io/client-go/rest"
3031
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -59,16 +60,26 @@ func New(config *rest.Config, options Options) (Client, error) {
5960
}
6061
}
6162

63+
dynamicClient, err := dynamic.NewForConfig(config)
64+
if err != nil {
65+
return nil, err
66+
}
67+
6268
c := &client{
63-
cache: clientCache{
64-
config: config,
65-
scheme: options.Scheme,
66-
mapper: options.Mapper,
67-
codecs: serializer.NewCodecFactory(options.Scheme),
68-
resourceByType: make(map[reflect.Type]*resourceMeta),
69-
unstructuredResourceByGVK: make(map[schema.GroupVersionKind]*resourceMeta),
69+
typedClient: typedClient{
70+
cache: clientCache{
71+
config: config,
72+
scheme: options.Scheme,
73+
mapper: options.Mapper,
74+
codecs: serializer.NewCodecFactory(options.Scheme),
75+
resourceByType: make(map[reflect.Type]*resourceMeta),
76+
},
77+
paramCodec: runtime.NewParameterCodec(options.Scheme),
78+
},
79+
unstructuredClient: unstructuredClient{
80+
client: dynamicClient,
81+
restMapper: options.Mapper,
7082
},
71-
paramCodec: runtime.NewParameterCodec(options.Scheme),
7283
}
7384

7485
return c, nil
@@ -79,84 +90,53 @@ var _ Client = &client{}
7990
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
8091
// new clients at the time they are used, and caches the client.
8192
type client struct {
82-
cache clientCache
83-
paramCodec runtime.ParameterCodec
93+
typedClient typedClient
94+
unstructuredClient unstructuredClient
8495
}
8596

8697
// Create implements client.Client
8798
func (c *client) Create(ctx context.Context, obj runtime.Object) error {
88-
o, err := c.cache.getObjMeta(obj)
89-
if err != nil {
90-
return err
99+
_, ok := obj.(*unstructured.Unstructured)
100+
if ok {
101+
return c.unstructuredClient.Create(ctx, obj)
91102
}
92-
return o.Post().
93-
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
94-
Resource(o.resource()).
95-
Body(obj).
96-
Do().
97-
Into(obj)
103+
return c.typedClient.Create(ctx, obj)
98104
}
99105

100106
// Update implements client.Client
101107
func (c *client) Update(ctx context.Context, obj runtime.Object) error {
102-
o, err := c.cache.getObjMeta(obj)
103-
if err != nil {
104-
return err
108+
_, ok := obj.(*unstructured.Unstructured)
109+
if ok {
110+
return c.unstructuredClient.Update(ctx, obj)
105111
}
106-
return o.Put().
107-
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
108-
Resource(o.resource()).
109-
Name(o.GetName()).
110-
Body(obj).
111-
Do().
112-
Into(obj)
112+
return c.typedClient.Update(ctx, obj)
113113
}
114114

115115
// Delete implements client.Client
116116
func (c *client) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error {
117-
o, err := c.cache.getObjMeta(obj)
118-
if err != nil {
119-
return err
117+
_, ok := obj.(*unstructured.Unstructured)
118+
if ok {
119+
return c.unstructuredClient.Delete(ctx, obj, opts...)
120120
}
121-
122-
deleteOpts := DeleteOptions{}
123-
return o.Delete().
124-
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
125-
Resource(o.resource()).
126-
Name(o.GetName()).
127-
Body(deleteOpts.ApplyOptions(opts).AsDeleteOptions()).
128-
Do().
129-
Error()
121+
return c.typedClient.Delete(ctx, obj, opts...)
130122
}
131123

132124
// Get implements client.Client
133125
func (c *client) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
134-
r, err := c.cache.getResource(obj)
135-
if err != nil {
136-
return err
126+
_, ok := obj.(*unstructured.Unstructured)
127+
if ok {
128+
return c.unstructuredClient.Get(ctx, key, obj)
137129
}
138-
return r.Get().
139-
NamespaceIfScoped(key.Namespace, r.isNamespaced()).
140-
Resource(r.resource()).
141-
Name(key.Name).Do().Into(obj)
130+
return c.typedClient.Get(ctx, key, obj)
142131
}
143132

144133
// List implements client.Client
145134
func (c *client) List(ctx context.Context, obj runtime.Object, opts ...ListOptionFunc) error {
146-
r, err := c.cache.getResource(obj)
147-
if err != nil {
148-
return err
135+
_, ok := obj.(*unstructured.UnstructuredList)
136+
if ok {
137+
return c.unstructuredClient.List(ctx, obj, opts...)
149138
}
150-
151-
listOpts := ListOptions{}
152-
listOpts.ApplyOptions(opts)
153-
return r.Get().
154-
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
155-
Resource(r.resource()).
156-
Body(obj).
157-
VersionedParams(listOpts.AsListOptions(), c.paramCodec).
158-
Do().
159-
Into(obj)
139+
return c.typedClient.List(ctx, obj, opts...)
160140
}
161141

162142
// Status implements client.StatusClient
@@ -174,7 +154,7 @@ var _ StatusWriter = &statusWriter{}
174154

175155
// Update implements client.StatusWriter
176156
func (sw *statusWriter) Update(_ context.Context, obj runtime.Object) error {
177-
o, err := sw.client.cache.getObjMeta(obj)
157+
o, err := sw.client.typedClient.cache.getObjMeta(obj)
178158
if err != nil {
179159
return err
180160
}

pkg/client/client_cache.go

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323

2424
"k8s.io/apimachinery/pkg/api/meta"
2525
"k8s.io/apimachinery/pkg/apis/meta/v1"
26-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2726
"k8s.io/apimachinery/pkg/runtime"
2827
"k8s.io/apimachinery/pkg/runtime/schema"
2928
"k8s.io/apimachinery/pkg/runtime/serializer"
@@ -45,18 +44,14 @@ type clientCache struct {
4544
// codecs are used to create a REST client for a gvk
4645
codecs serializer.CodecFactory
4746

48-
muByType sync.RWMutex
4947
// resourceByType caches type metadata
5048
resourceByType map[reflect.Type]*resourceMeta
51-
52-
muByGVK sync.RWMutex
53-
// resourceByGVK caches type metadata for unstructured
54-
unstructuredResourceByGVK map[schema.GroupVersionKind]*resourceMeta
49+
mu sync.RWMutex
5550
}
5651

5752
// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
5853
// If the object is a list, the resource represents the item's type instead.
59-
func (c *clientCache) newResource(obj runtime.Object, isUnstructured bool) (*resourceMeta, error) {
54+
func (c *clientCache) newResource(obj runtime.Object) (*resourceMeta, error) {
6055
gvk, err := apiutil.GVKForObject(obj, c.scheme)
6156
if err != nil {
6257
return nil, err
@@ -67,12 +62,7 @@ func (c *clientCache) newResource(obj runtime.Object, isUnstructured bool) (*res
6762
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
6863
}
6964

70-
var client rest.Interface
71-
if isUnstructured {
72-
client, err = apiutil.RESTUnstructuredClientForGVK(gvk, c.config)
73-
} else {
74-
client, err = apiutil.RESTClientForGVK(gvk, c.config, c.codecs)
75-
}
65+
client, err := apiutil.RESTClientForGVK(gvk, c.config, c.codecs)
7666
if err != nil {
7767
return nil, err
7868
}
@@ -83,63 +73,32 @@ func (c *clientCache) newResource(obj runtime.Object, isUnstructured bool) (*res
8373
return &resourceMeta{Interface: client, mapping: mapping, gvk: gvk}, nil
8474
}
8575

86-
func (c *clientCache) getUnstructuredResourceByGVK(obj runtime.Object) (*resourceMeta, error) {
87-
// It's better to do creation work twice than to not let multiple
88-
// people make requests at once
89-
c.muByGVK.RLock()
90-
r, known := c.unstructuredResourceByGVK[obj.GetObjectKind().GroupVersionKind()]
91-
c.muByGVK.RUnlock()
92-
93-
if known {
94-
return r, nil
95-
}
96-
97-
// Initialize a new Client
98-
c.muByGVK.Lock()
99-
defer c.muByGVK.Unlock()
100-
r, err := c.newResource(obj, true)
101-
if err != nil {
102-
return nil, err
103-
}
104-
c.unstructuredResourceByGVK[obj.GetObjectKind().GroupVersionKind()] = r
105-
return r, err
106-
}
107-
108-
func (c *clientCache) getResourceByType(obj runtime.Object) (*resourceMeta, error) {
76+
// getResource returns the resource meta information for the given type of object.
77+
// If the object is a list, the resource represents the item's type instead.
78+
func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
10979
typ := reflect.TypeOf(obj)
11080

11181
// It's better to do creation work twice than to not let multiple
11282
// people make requests at once
113-
c.muByType.RLock()
83+
c.mu.RLock()
11484
r, known := c.resourceByType[typ]
115-
c.muByType.RUnlock()
85+
c.mu.RUnlock()
11686

11787
if known {
11888
return r, nil
11989
}
12090

12191
// Initialize a new Client
122-
c.muByType.Lock()
123-
defer c.muByType.Unlock()
124-
r, err := c.newResource(obj, false)
92+
c.mu.Lock()
93+
defer c.mu.Unlock()
94+
r, err := c.newResource(obj)
12595
if err != nil {
12696
return nil, err
12797
}
12898
c.resourceByType[typ] = r
12999
return r, err
130100
}
131101

132-
// getResource returns the resource meta information for the given type of object.
133-
// If the object is a list, the resource represents the item's type instead.
134-
func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
135-
_, isUnstructured := obj.(*unstructured.Unstructured)
136-
_, isUnstructuredList := obj.(*unstructured.UnstructuredList)
137-
if isUnstructured || isUnstructuredList {
138-
return c.getUnstructuredResourceByGVK(obj)
139-
}
140-
return c.getResourceByType(obj)
141-
}
142-
143102
// getObjMeta returns objMeta containing both type and object metadata and state
144103
func (c *clientCache) getObjMeta(obj runtime.Object) (*objMeta, error) {
145104
r, err := c.getResource(obj)

0 commit comments

Comments
 (0)