Skip to content

Commit fcc5bec

Browse files
Add UnsafeDisableDeepCopy to GetOptions
1 parent 71f7db5 commit fcc5bec

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

pkg/cache/internal/cache_reader.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ type CacheReader struct {
5454
}
5555

5656
// Get checks the indexer for the object and writes a copy of it if found.
57-
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, _ ...client.GetOption) error {
57+
func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Object, opts ...client.GetOption) error {
58+
getOpts := client.GetOptions{}
59+
getOpts.ApplyOptions(opts)
60+
5861
if c.scopeName == apimeta.RESTScopeNameRoot {
5962
key.Namespace = ""
6063
}
@@ -81,7 +84,7 @@ func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Ob
8184
return fmt.Errorf("cache contained %T, which is not an Object", obj)
8285
}
8386

84-
if c.disableDeepCopy {
87+
if c.disableDeepCopy || (getOpts.UnsafeDisableDeepCopy != nil && *getOpts.UnsafeDisableDeepCopy) {
8588
// skip deep copy which might be unsafe
8689
// you must DeepCopy any object before mutating it outside
8790
} else {
@@ -97,7 +100,7 @@ func (c *CacheReader) Get(_ context.Context, key client.ObjectKey, out client.Ob
97100
return fmt.Errorf("cache had type %s, but %s was asked for", objVal.Type(), outVal.Type())
98101
}
99102
reflect.Indirect(outVal).Set(reflect.Indirect(objVal))
100-
if !c.disableDeepCopy {
103+
if !c.disableDeepCopy && (getOpts.UnsafeDisableDeepCopy == nil || !*getOpts.UnsafeDisableDeepCopy) {
101104
out.GetObjectKind().SetGroupVersionKind(c.groupVersionKind)
102105
}
103106

pkg/client/client_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1976,6 +1976,30 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
19761976

19771977
})
19781978

1979+
It("should not deep copy results when UnsafeDisableDeepCopy is set", func() {
1980+
By("first creating the Deployment")
1981+
dep, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
1982+
Expect(err).NotTo(HaveOccurred())
1983+
1984+
cl, err := client.New(cfg, client.Options{})
1985+
Expect(err).NotTo(HaveOccurred())
1986+
Expect(cl).NotTo(BeNil())
1987+
1988+
By("fetching the created Deployment")
1989+
var actual appsv1.Deployment
1990+
key := client.ObjectKey{Namespace: ns, Name: dep.Name}
1991+
err = cl.Get(context.TODO(), key, &actual, client.UnsafeDisableDeepCopy)
1992+
Expect(err).NotTo(HaveOccurred())
1993+
Expect(actual).NotTo(BeNil())
1994+
1995+
var newActual appsv1.Deployment
1996+
err = cl.Get(context.TODO(), key, &newActual, client.UnsafeDisableDeepCopy)
1997+
Expect(err).NotTo(HaveOccurred())
1998+
Expect(newActual).NotTo(BeNil())
1999+
2000+
Expect(newActual).To(BeIdenticalTo(actual))
2001+
})
2002+
19792003
// Test this with an integrated type and a CRD to make sure it covers both proto
19802004
// and json deserialization.
19812005
for idx, object := range []client.Object{&corev1.ConfigMap{}, &pkg.ChaosPod{}} {
@@ -2647,6 +2671,33 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
26472671
Expect(deps.Items[1].Name).To(Equal(dep4.Name))
26482672
})
26492673

2674+
It("should not deep copy results when UnsafeDisableDeepCopy is set", func() {
2675+
By("creating an initial object")
2676+
_, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
2677+
Expect(err).NotTo(HaveOccurred())
2678+
2679+
cl, err := client.New(cfg, client.Options{})
2680+
Expect(err).NotTo(HaveOccurred())
2681+
2682+
By("listing all objects of that type in the cluster")
2683+
deps := &appsv1.DeploymentList{}
2684+
Expect(cl.List(context.Background(), deps), client.UnsafeDisableDeepCopy).NotTo(HaveOccurred())
2685+
2686+
Expect(deps.Items).NotTo(BeEmpty())
2687+
2688+
By("listing all objects of that type again in the cluster")
2689+
newDeps := &appsv1.DeploymentList{}
2690+
Expect(cl.List(context.Background(), newDeps), client.UnsafeDisableDeepCopy).NotTo(HaveOccurred())
2691+
2692+
Expect(newDeps.Items).NotTo(BeEmpty())
2693+
2694+
Expect(newDeps).To(HaveLen(len(deps.Items)))
2695+
// Expect the elements to be the same since we didn't DeepCopy them
2696+
for i := range deps.Items {
2697+
Expect(deps.Items[i]).To(BeIdenticalTo(newDeps.Items[i]))
2698+
}
2699+
})
2700+
26502701
PIt("should fail if the object doesn't have meta", func() {
26512702

26522703
})

pkg/client/options.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@ type GetOptions struct {
431431
// Raw represents raw GetOptions, as passed to the API server. Note
432432
// that these may not be respected by all implementations of interface.
433433
Raw *metav1.GetOptions
434+
435+
// UnsafeDisableDeepCopy indicates not to deep copy objects during get object.
436+
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
437+
// otherwise you will mutate the object in the cache.
438+
// +optional
439+
UnsafeDisableDeepCopy *bool
434440
}
435441

436442
var _ GetOption = &GetOptions{}
@@ -440,6 +446,9 @@ func (o *GetOptions) ApplyToGet(lo *GetOptions) {
440446
if o.Raw != nil {
441447
lo.Raw = o.Raw
442448
}
449+
if o.UnsafeDisableDeepCopy != nil {
450+
lo.UnsafeDisableDeepCopy = o.UnsafeDisableDeepCopy
451+
}
443452
}
444453

445454
// AsGetOptions returns these options as a flattened metav1.GetOptions.
@@ -692,6 +701,17 @@ func (l Limit) ApplyToList(opts *ListOptions) {
692701
// otherwise you will mutate the object in the cache.
693702
type UnsafeDisableDeepCopyOption bool
694703

704+
// ApplyToGet applies this configuration to the given an Get options.
705+
func (d UnsafeDisableDeepCopyOption) ApplyToGet(opts *GetOptions) {
706+
definitelyTrue := true
707+
definitelyFalse := false
708+
if d {
709+
opts.UnsafeDisableDeepCopy = &definitelyTrue
710+
} else {
711+
opts.UnsafeDisableDeepCopy = &definitelyFalse
712+
}
713+
}
714+
695715
// ApplyToList applies this configuration to the given an List options.
696716
func (d UnsafeDisableDeepCopyOption) ApplyToList(opts *ListOptions) {
697717
definitelyTrue := true

pkg/client/options_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ var _ = Describe("ListOptions", func() {
6666
o.ApplyToList(newListOpts)
6767
Expect(newListOpts).To(Equal(o))
6868
})
69+
It("Should set UnsafeDisableDeepCopy", func() {
70+
definitelyTrue := true
71+
o := &client.ListOptions{UnsafeDisableDeepCopy: &definitelyTrue}
72+
newListOpts := &client.ListOptions{}
73+
o.ApplyToList(newListOpts)
74+
Expect(newListOpts).To(Equal(o))
75+
})
76+
It("Should set UnsafeDisableDeepCopy through option", func() {
77+
listOpts := &client.ListOptions{}
78+
client.UnsafeDisableDeepCopy.ApplyToList(listOpts)
79+
Expect(listOpts.UnsafeDisableDeepCopy).ToNot(BeNil())
80+
Expect(*listOpts.UnsafeDisableDeepCopy).To(BeTrue())
81+
})
6982
It("Should not set anything", func() {
7083
o := &client.ListOptions{}
7184
newListOpts := &client.ListOptions{}
@@ -81,6 +94,19 @@ var _ = Describe("GetOptions", func() {
8194
o.ApplyToGet(newGetOpts)
8295
Expect(newGetOpts).To(Equal(o))
8396
})
97+
It("Should set UnsafeDisableDeepCopy", func() {
98+
definitelyTrue := true
99+
o := &client.GetOptions{UnsafeDisableDeepCopy: &definitelyTrue}
100+
newGetOpts := &client.GetOptions{}
101+
o.ApplyToGet(newGetOpts)
102+
Expect(newGetOpts).To(Equal(o))
103+
})
104+
It("Should set UnsafeDisableDeepCopy through option", func() {
105+
getOpts := &client.GetOptions{}
106+
client.UnsafeDisableDeepCopy.ApplyToGet(getOpts)
107+
Expect(getOpts.UnsafeDisableDeepCopy).ToNot(BeNil())
108+
Expect(*getOpts.UnsafeDisableDeepCopy).To(BeTrue())
109+
})
84110
})
85111

86112
var _ = Describe("CreateOptions", func() {

0 commit comments

Comments
 (0)