Skip to content

Commit d2c3550

Browse files
committed
Remove DelegatedClient, move Options in client.New
Signed-off-by: Vince Prignano <[email protected]>
1 parent 375a05e commit d2c3550

12 files changed

+158
-270
lines changed

pkg/client/client.go

Lines changed: 91 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ import (
3636
"sigs.k8s.io/controller-runtime/pkg/log"
3737
)
3838

39+
// Options are creation options for a Client.
40+
type Options struct {
41+
// Scheme, if provided, will be used to map go structs to GroupVersionKinds
42+
Scheme *runtime.Scheme
43+
44+
// Mapper, if provided, will be used to map GroupVersionKinds to Resources
45+
Mapper meta.RESTMapper
46+
47+
// Cache, if provided, is used to read objects from the cache.
48+
Cache *CacheOptions
49+
50+
// WarningHandler is used to configure the warning handler responsible for
51+
// surfacing and handling warnings messages sent by the API server.
52+
WarningHandler WarningHandlerOptions
53+
}
54+
3955
// WarningHandlerOptions are options for configuring a
4056
// warning handler for the client which is responsible
4157
// for surfacing API Server warnings.
@@ -50,19 +66,21 @@ type WarningHandlerOptions struct {
5066
AllowDuplicateLogs bool
5167
}
5268

53-
// Options are creation options for a Client.
54-
type Options struct {
55-
// Scheme, if provided, will be used to map go structs to GroupVersionKinds
56-
Scheme *runtime.Scheme
57-
58-
// Mapper, if provided, will be used to map GroupVersionKinds to Resources
59-
Mapper meta.RESTMapper
60-
61-
// Opts is used to configure the warning handler responsible for
62-
// surfacing and handling warnings messages sent by the API server.
63-
Opts WarningHandlerOptions
69+
// CacheOptions are options for creating a cache-backed client.
70+
type CacheOptions struct {
71+
// Reader is a cache-backed reader that will be used to read objects from the cache.
72+
// +required
73+
Reader Reader
74+
// DisableFor is a list of objects that should not be read from the cache.
75+
DisableFor []Object
76+
// Unstructured is a flag that indicates whether the cache-backed client should
77+
// read unstructured objects or lists from the cache.
78+
Unstructured bool
6479
}
6580

81+
// NewClientFunc allows a user to define how to create a client.
82+
type NewClientFunc func(config *rest.Config, options Options) (Client, error)
83+
6684
// New returns a new Client using the provided config and Options.
6785
// The returned client reads *and* writes directly from the server
6886
// (it doesn't use object caches). It understands how to work with
@@ -82,7 +100,7 @@ func newClient(config *rest.Config, options Options) (*client, error) {
82100
return nil, fmt.Errorf("must provide non-nil rest.Config to client.New")
83101
}
84102

85-
if !options.Opts.SuppressWarnings {
103+
if !options.WarningHandler.SuppressWarnings {
86104
// surface warnings
87105
logger := log.Log.WithName("KubeAPIWarningLogger")
88106
// Set a WarningHandler, the default WarningHandler
@@ -93,7 +111,7 @@ func newClient(config *rest.Config, options Options) (*client, error) {
93111
config.WarningHandler = log.NewKubeAPIWarningLogger(
94112
logger,
95113
log.KubeAPIWarningLoggerOptions{
96-
Deduplicate: !options.Opts.AllowDuplicateLogs,
114+
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
97115
},
98116
)
99117
}
@@ -143,7 +161,24 @@ func newClient(config *rest.Config, options Options) (*client, error) {
143161
scheme: options.Scheme,
144162
mapper: options.Mapper,
145163
}
164+
if options.Cache == nil || options.Cache.Reader == nil {
165+
return c, nil
166+
}
167+
168+
// We want a cache if we're here.
169+
// Set the cache.
170+
c.cache = options.Cache.Reader
146171

172+
// Load uncached GVKs.
173+
c.cacheUnstructured = options.Cache.Unstructured
174+
uncachedGVKs := map[schema.GroupVersionKind]struct{}{}
175+
for _, obj := range options.Cache.DisableFor {
176+
gvk, err := c.GroupVersionKindFor(obj)
177+
if err != nil {
178+
return nil, err
179+
}
180+
uncachedGVKs[gvk] = struct{}{}
181+
}
147182
return c, nil
148183
}
149184

@@ -157,6 +192,35 @@ type client struct {
157192
metadataClient metadataClient
158193
scheme *runtime.Scheme
159194
mapper meta.RESTMapper
195+
196+
cache Reader
197+
uncachedGVKs map[schema.GroupVersionKind]struct{}
198+
cacheUnstructured bool
199+
}
200+
201+
func (c *client) shouldBypassCache(obj runtime.Object) (bool, error) {
202+
if c.cache == nil {
203+
return true, nil
204+
}
205+
206+
gvk, err := c.GroupVersionKindFor(obj)
207+
if err != nil {
208+
return false, err
209+
}
210+
// TODO: this is producing unsafe guesses that don't actually work,
211+
// but it matches ~99% of the cases out there.
212+
if meta.IsListType(obj) {
213+
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
214+
}
215+
if _, isUncached := c.uncachedGVKs[gvk]; isUncached {
216+
return true, nil
217+
}
218+
if !c.cacheUnstructured {
219+
_, isUnstructured := obj.(*unstructured.Unstructured)
220+
_, isUnstructuredList := obj.(*unstructured.UnstructuredList)
221+
return isUnstructured || isUnstructuredList, nil
222+
}
223+
return false, nil
160224
}
161225

162226
// resetGroupVersionKind is a helper function to restore and preserve GroupVersionKind on an object.
@@ -169,12 +233,12 @@ func (c *client) resetGroupVersionKind(obj runtime.Object, gvk schema.GroupVersi
169233
}
170234

171235
// GroupVersionKindFor returns the GroupVersionKind for the given object.
172-
func (c *client) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
236+
func (c *client) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
173237
return apiutil.GVKForObject(obj, c.scheme)
174238
}
175239

176240
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
177-
func (c *client) IsObjectNamespaced(obj Object) (bool, error) {
241+
func (c *client) IsObjectNamespaced(obj runtime.Object) (bool, error) {
178242
return apiutil.IsObjectNamespaced(obj, c.scheme, c.mapper)
179243
}
180244

@@ -252,6 +316,12 @@ func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...Pat
252316

253317
// Get implements client.Client.
254318
func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
319+
if isUncached, err := c.shouldBypassCache(obj); err != nil {
320+
return err
321+
} else if !isUncached {
322+
return c.cache.Get(ctx, key, obj, opts...)
323+
}
324+
255325
switch obj.(type) {
256326
case *unstructured.Unstructured:
257327
return c.unstructuredClient.Get(ctx, key, obj, opts...)
@@ -266,6 +336,12 @@ func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...Get
266336

267337
// List implements client.Client.
268338
func (c *client) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
339+
if isUncached, err := c.shouldBypassCache(obj); err != nil {
340+
return err
341+
} else if !isUncached {
342+
return c.cache.List(ctx, obj, opts...)
343+
}
344+
269345
switch x := obj.(type) {
270346
case *unstructured.UnstructuredList:
271347
return c.unstructuredClient.List(ctx, obj, opts...)

pkg/client/client_test.go

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3541,20 +3541,19 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
35413541
})
35423542
})
35433543

3544-
var _ = Describe("DelegatingClient", func() {
3544+
var _ = Describe("ClientWithCache", func() {
35453545
Describe("Get", func() {
35463546
It("should call cache reader when structured object", func() {
35473547
cachedReader := &fakeReader{}
3548-
cl, err := client.New(cfg, client.Options{})
3549-
Expect(err).NotTo(HaveOccurred())
3550-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3551-
CacheReader: cachedReader,
3552-
Client: cl,
3548+
cl, err := client.New(cfg, client.Options{
3549+
Cache: &client.CacheOptions{
3550+
Reader: cachedReader,
3551+
},
35533552
})
35543553
Expect(err).NotTo(HaveOccurred())
35553554
var actual appsv1.Deployment
35563555
key := client.ObjectKey{Namespace: "ns", Name: "name"}
3557-
Expect(dReader.Get(context.TODO(), key, &actual)).To(Succeed())
3556+
Expect(cl.Get(context.TODO(), key, &actual)).To(Succeed())
35583557
Expect(1).To(Equal(cachedReader.Called))
35593558
})
35603559

@@ -3590,11 +3589,10 @@ var _ = Describe("DelegatingClient", func() {
35903589
})
35913590
It("should call client reader when not cached", func() {
35923591
cachedReader := &fakeReader{}
3593-
cl, err := client.New(cfg, client.Options{})
3594-
Expect(err).NotTo(HaveOccurred())
3595-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3596-
CacheReader: cachedReader,
3597-
Client: cl,
3592+
cl, err := client.New(cfg, client.Options{
3593+
Cache: &client.CacheOptions{
3594+
Reader: cachedReader,
3595+
},
35983596
})
35993597
Expect(err).NotTo(HaveOccurred())
36003598

@@ -3606,17 +3604,16 @@ var _ = Describe("DelegatingClient", func() {
36063604
})
36073605
actual.SetName(dep.Name)
36083606
key := client.ObjectKey{Namespace: dep.Namespace, Name: dep.Name}
3609-
Expect(dReader.Get(context.TODO(), key, actual)).To(Succeed())
3607+
Expect(cl.Get(context.TODO(), key, actual)).To(Succeed())
36103608
Expect(0).To(Equal(cachedReader.Called))
36113609
})
36123610
It("should call cache reader when cached", func() {
36133611
cachedReader := &fakeReader{}
3614-
cl, err := client.New(cfg, client.Options{})
3615-
Expect(err).NotTo(HaveOccurred())
3616-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3617-
CacheReader: cachedReader,
3618-
Client: cl,
3619-
CacheUnstructured: true,
3612+
cl, err := client.New(cfg, client.Options{
3613+
Cache: &client.CacheOptions{
3614+
Reader: cachedReader,
3615+
Unstructured: true,
3616+
},
36203617
})
36213618
Expect(err).NotTo(HaveOccurred())
36223619

@@ -3628,34 +3625,32 @@ var _ = Describe("DelegatingClient", func() {
36283625
})
36293626
actual.SetName(dep.Name)
36303627
key := client.ObjectKey{Namespace: dep.Namespace, Name: dep.Name}
3631-
Expect(dReader.Get(context.TODO(), key, actual)).To(Succeed())
3628+
Expect(cl.Get(context.TODO(), key, actual)).To(Succeed())
36323629
Expect(1).To(Equal(cachedReader.Called))
36333630
})
36343631
})
36353632
})
36363633
Describe("List", func() {
36373634
It("should call cache reader when structured object", func() {
36383635
cachedReader := &fakeReader{}
3639-
cl, err := client.New(cfg, client.Options{})
3640-
Expect(err).NotTo(HaveOccurred())
3641-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3642-
CacheReader: cachedReader,
3643-
Client: cl,
3636+
cl, err := client.New(cfg, client.Options{
3637+
Cache: &client.CacheOptions{
3638+
Reader: cachedReader,
3639+
},
36443640
})
36453641
Expect(err).NotTo(HaveOccurred())
36463642
var actual appsv1.DeploymentList
3647-
Expect(dReader.List(context.Background(), &actual)).To(Succeed())
3643+
Expect(cl.List(context.Background(), &actual)).To(Succeed())
36483644
Expect(1).To(Equal(cachedReader.Called))
36493645
})
36503646

36513647
When("listing unstructured objects", func() {
36523648
It("should call client reader when not cached", func() {
36533649
cachedReader := &fakeReader{}
3654-
cl, err := client.New(cfg, client.Options{})
3655-
Expect(err).NotTo(HaveOccurred())
3656-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3657-
CacheReader: cachedReader,
3658-
Client: cl,
3650+
cl, err := client.New(cfg, client.Options{
3651+
Cache: &client.CacheOptions{
3652+
Reader: cachedReader,
3653+
},
36593654
})
36603655
Expect(err).NotTo(HaveOccurred())
36613656

@@ -3665,17 +3660,16 @@ var _ = Describe("DelegatingClient", func() {
36653660
Kind: "DeploymentList",
36663661
Version: "v1",
36673662
})
3668-
Expect(dReader.List(context.Background(), actual)).To(Succeed())
3663+
Expect(cl.List(context.Background(), actual)).To(Succeed())
36693664
Expect(0).To(Equal(cachedReader.Called))
36703665
})
36713666
It("should call cache reader when cached", func() {
36723667
cachedReader := &fakeReader{}
3673-
cl, err := client.New(cfg, client.Options{})
3674-
Expect(err).NotTo(HaveOccurred())
3675-
dReader, err := client.NewDelegatingClient(client.NewDelegatingClientInput{
3676-
CacheReader: cachedReader,
3677-
Client: cl,
3678-
CacheUnstructured: true,
3668+
cl, err := client.New(cfg, client.Options{
3669+
Cache: &client.CacheOptions{
3670+
Reader: cachedReader,
3671+
Unstructured: true,
3672+
},
36793673
})
36803674
Expect(err).NotTo(HaveOccurred())
36813675

@@ -3685,7 +3679,7 @@ var _ = Describe("DelegatingClient", func() {
36853679
Kind: "DeploymentList",
36863680
Version: "v1",
36873681
})
3688-
Expect(dReader.List(context.Background(), actual)).To(Succeed())
3682+
Expect(cl.List(context.Background(), actual)).To(Succeed())
36893683
Expect(1).To(Equal(cachedReader.Called))
36903684
})
36913685
})

pkg/client/doc.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ limitations under the License.
2626
// to the API server.
2727
//
2828
// It is a common pattern in Kubernetes to read from a cache and write to the API
29-
// server. This pattern is covered by the DelegatingClient type, which can
30-
// be used to have a client whose Reader is different from the Writer.
29+
// server. This pattern is covered by the creating the Client with a Cache.
3130
//
3231
// # Options
3332
//

pkg/client/dryrun.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ func (c *dryRunClient) RESTMapper() meta.RESTMapper {
4848
}
4949

5050
// GroupVersionKindFor returns the GroupVersionKind for the given object.
51-
func (c *dryRunClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
51+
func (c *dryRunClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
5252
return c.client.GroupVersionKindFor(obj)
5353
}
5454

5555
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
56-
func (c *dryRunClient) IsObjectNamespaced(obj Object) (bool, error) {
56+
func (c *dryRunClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
5757
return c.client.IsObjectNamespaced(obj)
5858
}
5959

pkg/client/fake/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,12 +564,12 @@ func (c *fakeClient) RESTMapper() meta.RESTMapper {
564564
}
565565

566566
// GroupVersionKindFor returns the GroupVersionKind for the given object.
567-
func (c *fakeClient) GroupVersionKindFor(obj client.Object) (schema.GroupVersionKind, error) {
567+
func (c *fakeClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
568568
return apiutil.GVKForObject(obj, c.scheme)
569569
}
570570

571571
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
572-
func (c *fakeClient) IsObjectNamespaced(obj client.Object) (bool, error) {
572+
func (c *fakeClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
573573
return apiutil.IsObjectNamespaced(obj, c.scheme, c.restMapper)
574574
}
575575

pkg/client/interfaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,9 @@ type Client interface {
171171
// RESTMapper returns the rest this client is using.
172172
RESTMapper() meta.RESTMapper
173173
// GroupVersionKindFor returns the GroupVersionKind for the given object.
174-
GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error)
174+
GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error)
175175
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
176-
IsObjectNamespaced(obj Object) (bool, error)
176+
IsObjectNamespaced(obj runtime.Object) (bool, error)
177177
}
178178

179179
// WithWatch supports Watch on top of the CRUD operations supported by

pkg/client/namespaced_client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ func (n *namespacedClient) RESTMapper() meta.RESTMapper {
5353
}
5454

5555
// GroupVersionKindFor returns the GroupVersionKind for the given object.
56-
func (n *namespacedClient) GroupVersionKindFor(obj Object) (schema.GroupVersionKind, error) {
56+
func (n *namespacedClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
5757
return n.client.GroupVersionKindFor(obj)
5858
}
5959

6060
// IsObjectNamespaced returns true if the GroupVersionKind of the object is namespaced.
61-
func (n *namespacedClient) IsObjectNamespaced(obj Object) (bool, error) {
61+
func (n *namespacedClient) IsObjectNamespaced(obj runtime.Object) (bool, error) {
6262
return n.client.IsObjectNamespaced(obj)
6363
}
6464

0 commit comments

Comments
 (0)