Skip to content

Commit 555c09a

Browse files
author
Shawn Hurley
committed
adding two types of client for unstructured and typed
1 parent 9ee6a2f commit 555c09a

File tree

4 files changed

+276
-113
lines changed

4 files changed

+276
-113
lines changed

pkg/client/client.go

Lines changed: 43 additions & 61 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,82 +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) 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)
120120
}
121-
return o.Delete().
122-
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
123-
Resource(o.resource()).
124-
Name(o.GetName()).
125-
Do().
126-
Error()
121+
return c.typedClient.Delete(ctx, obj)
127122
}
128123

129124
// Get implements client.Client
130125
func (c *client) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
131-
r, err := c.cache.getResource(obj)
132-
if err != nil {
133-
return err
126+
_, ok := obj.(*unstructured.Unstructured)
127+
if ok {
128+
return c.unstructuredClient.Get(ctx, key, obj)
134129
}
135-
return r.Get().
136-
NamespaceIfScoped(key.Namespace, r.isNamespaced()).
137-
Resource(r.resource()).
138-
Name(key.Name).Do().Into(obj)
130+
return c.typedClient.Get(ctx, key, obj)
139131
}
140132

141133
// List implements client.Client
142134
func (c *client) List(ctx context.Context, opts *ListOptions, obj runtime.Object) error {
143-
r, err := c.cache.getResource(obj)
144-
if err != nil {
145-
return err
135+
_, ok := obj.(*unstructured.Unstructured)
136+
if ok {
137+
return c.unstructuredClient.List(ctx, opts, obj)
146138
}
147-
namespace := ""
148-
if opts != nil {
149-
namespace = opts.Namespace
150-
}
151-
return r.Get().
152-
NamespaceIfScoped(namespace, r.isNamespaced()).
153-
Resource(r.resource()).
154-
Body(obj).
155-
VersionedParams(opts.AsListOptions(), c.paramCodec).
156-
Do().
157-
Into(obj)
139+
return c.typedClient.List(ctx, opts, obj)
158140
}
159141

160142
// Status implements client.StatusClient
@@ -172,7 +154,7 @@ var _ StatusWriter = &statusWriter{}
172154

173155
// Update implements client.StatusWriter
174156
func (sw *statusWriter) Update(_ context.Context, obj runtime.Object) error {
175-
o, err := sw.client.cache.getObjMeta(obj)
157+
o, err := sw.client.typedClient.cache.getObjMeta(obj)
176158
if err != nil {
177159
return err
178160
}

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)

pkg/client/typed_client.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package client
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apimachinery/pkg/runtime"
23+
)
24+
25+
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
26+
// new clients at the time they are used, and caches the client.
27+
type typedClient struct {
28+
cache clientCache
29+
paramCodec runtime.ParameterCodec
30+
}
31+
32+
// Create implements client.Client
33+
func (c *typedClient) Create(ctx context.Context, obj runtime.Object) error {
34+
o, err := c.cache.getObjMeta(obj)
35+
if err != nil {
36+
return err
37+
}
38+
return o.Post().
39+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
40+
Resource(o.resource()).
41+
Body(obj).
42+
Do().
43+
Into(obj)
44+
}
45+
46+
// Update implements client.Client
47+
func (c *typedClient) Update(ctx context.Context, obj runtime.Object) error {
48+
o, err := c.cache.getObjMeta(obj)
49+
if err != nil {
50+
return err
51+
}
52+
return o.Put().
53+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
54+
Resource(o.resource()).
55+
Name(o.GetName()).
56+
Body(obj).
57+
Do().
58+
Into(obj)
59+
}
60+
61+
// Delete implements client.Client
62+
func (c *typedClient) Delete(ctx context.Context, obj runtime.Object) error {
63+
o, err := c.cache.getObjMeta(obj)
64+
if err != nil {
65+
return err
66+
}
67+
return o.Delete().
68+
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
69+
Resource(o.resource()).
70+
Name(o.GetName()).
71+
Do().
72+
Error()
73+
}
74+
75+
// Get implements client.Client
76+
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
77+
r, err := c.cache.getResource(obj)
78+
if err != nil {
79+
return err
80+
}
81+
return r.Get().
82+
NamespaceIfScoped(key.Namespace, r.isNamespaced()).
83+
Resource(r.resource()).
84+
Name(key.Name).Do().Into(obj)
85+
}
86+
87+
// List implements client.Client
88+
func (c *typedClient) List(ctx context.Context, opts *ListOptions, obj runtime.Object) error {
89+
r, err := c.cache.getResource(obj)
90+
if err != nil {
91+
return err
92+
}
93+
namespace := ""
94+
if opts != nil {
95+
namespace = opts.Namespace
96+
}
97+
return r.Get().
98+
NamespaceIfScoped(namespace, r.isNamespaced()).
99+
Resource(r.resource()).
100+
Body(obj).
101+
VersionedParams(opts.AsListOptions(), c.paramCodec).
102+
Do().
103+
Into(obj)
104+
}

0 commit comments

Comments
 (0)