Skip to content

Commit 0374b8c

Browse files
authored
Merge pull request #796 from alvaroaleman/construct-pointer-for-non-pointers-only-with-test
🐛 Fix informer cache creating pointers to pointers
2 parents f284dc3 + bd6a4b5 commit 0374b8c

File tree

6 files changed

+206
-18
lines changed

6 files changed

+206
-18
lines changed

pkg/cache/informer_cache.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,47 +70,65 @@ func (ip *informerCache) Get(ctx context.Context, key client.ObjectKey, out runt
7070

7171
// List implements Reader
7272
func (ip *informerCache) List(ctx context.Context, out runtime.Object, opts ...client.ListOption) error {
73-
gvk, err := apiutil.GVKForObject(out, ip.Scheme)
73+
74+
gvk, cacheTypeObj, err := ip.objectTypeForListObject(out)
7475
if err != nil {
7576
return err
7677
}
7778

79+
started, cache, err := ip.InformersMap.Get(*gvk, cacheTypeObj)
80+
if err != nil {
81+
return err
82+
}
83+
84+
if !started {
85+
return &ErrCacheNotStarted{}
86+
}
87+
88+
return cache.Reader.List(ctx, out, opts...)
89+
}
90+
91+
// objectTypeForListObject tries to find the runtime.Object and associated GVK
92+
// for a single object corresponding to the passed-in list type. We need them
93+
// because they are used as cache map key.
94+
func (ip *informerCache) objectTypeForListObject(list runtime.Object) (*schema.GroupVersionKind, runtime.Object, error) {
95+
gvk, err := apiutil.GVKForObject(list, ip.Scheme)
96+
if err != nil {
97+
return nil, nil, err
98+
}
99+
78100
if !strings.HasSuffix(gvk.Kind, "List") {
79-
return fmt.Errorf("non-list type %T (kind %q) passed as output", out, gvk)
101+
return nil, nil, fmt.Errorf("non-list type %T (kind %q) passed as output", list, gvk)
80102
}
81103
// we need the non-list GVK, so chop off the "List" from the end of the kind
82104
gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
83-
_, isUnstructured := out.(*unstructured.UnstructuredList)
105+
_, isUnstructured := list.(*unstructured.UnstructuredList)
84106
var cacheTypeObj runtime.Object
85107
if isUnstructured {
86108
u := &unstructured.Unstructured{}
87109
u.SetGroupVersionKind(gvk)
88110
cacheTypeObj = u
89111
} else {
90-
itemsPtr, err := apimeta.GetItemsPtr(out)
112+
itemsPtr, err := apimeta.GetItemsPtr(list)
91113
if err != nil {
92-
return nil
114+
return nil, nil, err
93115
}
94116
// http://knowyourmeme.com/memes/this-is-fine
95117
elemType := reflect.Indirect(reflect.ValueOf(itemsPtr)).Type().Elem()
96-
cacheTypeValue := reflect.Zero(reflect.PtrTo(elemType))
118+
if elemType.Kind() != reflect.Ptr {
119+
elemType = reflect.PtrTo(elemType)
120+
}
121+
122+
cacheTypeValue := reflect.Zero(elemType)
97123
var ok bool
98124
cacheTypeObj, ok = cacheTypeValue.Interface().(runtime.Object)
99125
if !ok {
100-
return fmt.Errorf("cannot get cache for %T, its element %T is not a runtime.Object", out, cacheTypeValue.Interface())
126+
return nil, nil, fmt.Errorf("cannot get cache for %T, its element %T is not a runtime.Object", list, cacheTypeValue.Interface())
101127
}
102128
}
103129

104-
started, cache, err := ip.InformersMap.Get(gvk, cacheTypeObj)
105-
if err != nil {
106-
return err
107-
}
108-
109-
if !started {
110-
return &ErrCacheNotStarted{}
111-
}
130+
return &gvk, cacheTypeObj, nil
112131

113-
return cache.Reader.List(ctx, out, opts...)
114132
}
115133

116134
// GetInformerForKind returns the informer for the GroupVersionKind

pkg/cache/informer_cache_unit_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package cache
2+
3+
import (
4+
. "github.com/onsi/ginkgo"
5+
. "github.com/onsi/gomega"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
"k8s.io/client-go/kubernetes/scheme"
11+
12+
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
13+
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
14+
crscheme "sigs.k8s.io/controller-runtime/pkg/scheme"
15+
)
16+
17+
const (
18+
itemPointerSliceTypeGroupName = "jakob.fabian"
19+
itemPointerSliceTypeVersion = "v1"
20+
)
21+
22+
var _ = Describe("ip.objectTypeForListObject", func() {
23+
ip := &informerCache{
24+
InformersMap: &internal.InformersMap{Scheme: scheme.Scheme},
25+
}
26+
27+
It("should error on non-list types", func() {
28+
_, _, err := ip.objectTypeForListObject(&corev1.Pod{})
29+
Expect(err).To(HaveOccurred())
30+
Expect(err.Error()).To(Equal(`non-list type *v1.Pod (kind "/v1, Kind=Pod") passed as output`))
31+
})
32+
33+
It("should find the object type for unstructured lists", func() {
34+
unstructuredList := &unstructured.UnstructuredList{}
35+
unstructuredList.SetAPIVersion("v1")
36+
unstructuredList.SetKind("PodList")
37+
38+
gvk, obj, err := ip.objectTypeForListObject(unstructuredList)
39+
Expect(err).ToNot(HaveOccurred())
40+
Expect(gvk.Group).To(Equal(""))
41+
Expect(gvk.Version).To(Equal("v1"))
42+
Expect(gvk.Kind).To(Equal("Pod"))
43+
referenceUnstructured := &unstructured.Unstructured{}
44+
referenceUnstructured.SetGroupVersionKind(*gvk)
45+
Expect(obj).To(Equal(referenceUnstructured))
46+
47+
})
48+
49+
It("should find the object type of a list with a slice of literals items field", func() {
50+
gvk, obj, err := ip.objectTypeForListObject(&corev1.PodList{})
51+
Expect(err).ToNot(HaveOccurred())
52+
Expect(gvk.Group).To(Equal(""))
53+
Expect(gvk.Version).To(Equal("v1"))
54+
Expect(gvk.Kind).To(Equal("Pod"))
55+
var referencePod *corev1.Pod
56+
Expect(obj).To(Equal(referencePod))
57+
58+
})
59+
60+
It("should find the object type of a list with a slice of pointers items field", func() {
61+
By("registering the type", func() {
62+
err := (&crscheme.Builder{
63+
GroupVersion: schema.GroupVersion{Group: itemPointerSliceTypeGroupName, Version: itemPointerSliceTypeVersion},
64+
}).
65+
Register(
66+
&controllertest.UnconventionalListType{},
67+
&controllertest.UnconventionalListTypeList{},
68+
).AddToScheme(scheme.Scheme)
69+
Expect(err).To(BeNil())
70+
})
71+
72+
By("calling objectTypeForListObject", func() {
73+
gvk, obj, err := ip.objectTypeForListObject(&controllertest.UnconventionalListTypeList{})
74+
Expect(err).ToNot(HaveOccurred())
75+
Expect(gvk.Group).To(Equal(itemPointerSliceTypeGroupName))
76+
Expect(gvk.Version).To(Equal(itemPointerSliceTypeVersion))
77+
Expect(gvk.Kind).To(Equal("UnconventionalListType"))
78+
var referenceObject *controllertest.UnconventionalListType
79+
Expect(obj).To(Equal(referenceObject))
80+
})
81+
})
82+
})

pkg/controller/controller_integration_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/types"
2727
"sigs.k8s.io/controller-runtime/pkg/cache"
2828
"sigs.k8s.io/controller-runtime/pkg/controller"
29+
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
2930
"sigs.k8s.io/controller-runtime/pkg/handler"
3031
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3132
"sigs.k8s.io/controller-runtime/pkg/source"
@@ -167,6 +168,11 @@ var _ = Describe("controller", func() {
167168
Expect(err).NotTo(HaveOccurred())
168169
Expect(<-reconciled).To(Equal(expectedReconcileRequest))
169170

171+
By("Listing a type with a slice of pointers as items field")
172+
err = cm.GetClient().
173+
List(context.Background(), &controllertest.UnconventionalListTypeList{})
174+
Expect(err).NotTo(HaveOccurred())
175+
170176
close(done)
171177
}, 5)
172178
})

pkg/controller/controller_suite_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ import (
2121

2222
. "github.com/onsi/ginkgo"
2323
. "github.com/onsi/gomega"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
2425
"k8s.io/client-go/kubernetes"
26+
"k8s.io/client-go/kubernetes/scheme"
2527
"k8s.io/client-go/rest"
28+
29+
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
2630
"sigs.k8s.io/controller-runtime/pkg/envtest"
2731
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
2832
logf "sigs.k8s.io/controller-runtime/pkg/log"
2933
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3034
"sigs.k8s.io/controller-runtime/pkg/metrics"
35+
crscheme "sigs.k8s.io/controller-runtime/pkg/scheme"
3136
)
3237

3338
func TestSource(t *testing.T) {
@@ -42,9 +47,19 @@ var clientset *kubernetes.Clientset
4247
var _ = BeforeSuite(func(done Done) {
4348
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
4449

45-
testenv = &envtest.Environment{}
50+
err := (&crscheme.Builder{
51+
GroupVersion: schema.GroupVersion{Group: "chaosapps.metamagical.io", Version: "v1"},
52+
}).
53+
Register(
54+
&controllertest.UnconventionalListType{},
55+
&controllertest.UnconventionalListTypeList{},
56+
).AddToScheme(scheme.Scheme)
57+
Expect(err).To(BeNil())
58+
59+
testenv = &envtest.Environment{
60+
CRDDirectoryPaths: []string{"testdata/crds"},
61+
}
4662

47-
var err error
4863
cfg, err = testenv.Start()
4964
Expect(err).NotTo(HaveOccurred())
5065

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package controllertest
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
"k8s.io/apimachinery/pkg/runtime"
6+
)
7+
8+
var _ runtime.Object = &UnconventionalListType{}
9+
var _ runtime.Object = &UnconventionalListTypeList{}
10+
11+
// UnconventionalListType is used to test CRDs with List types that
12+
// have a slice of pointers rather than a slice of literals.
13+
type UnconventionalListType struct {
14+
metav1.TypeMeta `json:",inline"`
15+
metav1.ObjectMeta `json:"metadata,omitempty"`
16+
Spec string `json:"spec,omitempty"`
17+
}
18+
19+
// DeepCopyObject implements runtime.Object
20+
// Handwritten for simplicity.
21+
func (u *UnconventionalListType) DeepCopyObject() runtime.Object {
22+
return u.DeepCopy()
23+
}
24+
25+
func (u *UnconventionalListType) DeepCopy() *UnconventionalListType {
26+
return &UnconventionalListType{
27+
TypeMeta: u.TypeMeta,
28+
ObjectMeta: *u.ObjectMeta.DeepCopy(),
29+
Spec: u.Spec,
30+
}
31+
}
32+
33+
// UnconventionalListTypeList is used to test CRDs with List types that
34+
// have a slice of pointers rather than a slice of literals.
35+
type UnconventionalListTypeList struct {
36+
metav1.TypeMeta `json:",inline"`
37+
metav1.ListMeta `json:"metadata,omitempty"`
38+
Items []*UnconventionalListType `json:"items"`
39+
}
40+
41+
// DeepCopyObject implements runtime.Object
42+
// Handwritten for simplicity.
43+
func (u *UnconventionalListTypeList) DeepCopyObject() runtime.Object {
44+
return u.DeepCopy()
45+
}
46+
47+
func (u *UnconventionalListTypeList) DeepCopy() *UnconventionalListTypeList {
48+
out := &UnconventionalListTypeList{
49+
TypeMeta: u.TypeMeta,
50+
ListMeta: *u.ListMeta.DeepCopy(),
51+
}
52+
for _, item := range u.Items {
53+
out.Items = append(out.Items, item.DeepCopy())
54+
}
55+
return out
56+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: unconventionallisttypes.chaosapps.metamagical.io
5+
spec:
6+
group: chaosapps.metamagical.io
7+
names:
8+
kind: UnconventionalListType
9+
plural: unconventionallisttypes
10+
scope: Namespaced
11+
version: "v1"

0 commit comments

Comments
 (0)