Skip to content

Commit 20c034c

Browse files
deads2kk8s-publishing-bot
authored andcommitted
add GVK to fake dynamic client to match actual behavior
Kubernetes-commit: f4383458432cd67714e9ce0acde56a2ed5c24a21
1 parent 75ef13e commit 20c034c

File tree

3 files changed

+135
-14
lines changed

3 files changed

+135
-14
lines changed

dynamic/dynamicinformer/informer_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
9595
if ts.existingObj != nil {
9696
objs = append(objs, ts.existingObj)
9797
}
98-
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
98+
// don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
99+
// a client that doesn't have a type registered in the scheme.
100+
gvrToListKind := map[schema.GroupVersionResource]string{
101+
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
102+
}
103+
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
99104
target := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeClient, 0, ts.informNS, nil)
100105

101106
// act
@@ -214,7 +219,12 @@ func TestDynamicSharedInformerFactory(t *testing.T) {
214219
if ts.existingObj != nil {
215220
objs = append(objs, ts.existingObj)
216221
}
217-
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
222+
// don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
223+
// a client that doesn't have a type registered in the scheme.
224+
gvrToListKind := map[schema.GroupVersionResource]string{
225+
{Group: "extensions", Version: "v1beta1", Resource: "deployments"}: "DeploymentList",
226+
}
227+
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
218228
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
219229

220230
// act

dynamic/fake/simple.go

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package fake
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"strings"
2223

2324
"k8s.io/apimachinery/pkg/api/meta"
@@ -34,11 +35,45 @@ import (
3435
)
3536

3637
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
37-
// In order to use List with this client, you have to have the v1.List registered in your scheme. Neat thing though
38-
// it does NOT have to be the *same* list. UnstructuredList returned from this fake client will NOT have apiVersion and kind set,
39-
// but each Unstructured object in Items will preserve their respective apiVersion and kind. As a result, schema conversion for
40-
// *List kinds will not work and conversion of each Unstructured object in Items will be required instead.
41-
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "List"}, &unstructured.UnstructuredList{})
38+
return NewSimpleDynamicClientWithCustomListKinds(scheme, nil, objects...)
39+
}
40+
41+
// NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
42+
// and allow the default guessing for resources match. Sometimes that doesn't work, so you can specify a custom mapping here.
43+
func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
44+
// In order to use List with this client, you have to have your lists registered so that the object tracker will find them
45+
// in the scheme to support the t.scheme.New(listGVK) call when it's building the return value.
46+
// Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it
47+
// cannot look up the actual hits), we need to know a mapping of GVR to listGVK here. For GETs and other types of calls,
48+
// there is no return value that contains a GVK, so it doesn't have to know the mapping in advance.
49+
50+
// first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses
51+
// this covers common usage of registering types in scheme and passing them
52+
completeGVRToListKind := map[schema.GroupVersionResource]string{}
53+
for listGVK := range scheme.AllKnownTypes() {
54+
if !strings.HasSuffix(listGVK.Kind, "List") {
55+
continue
56+
}
57+
nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
58+
plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
59+
completeGVRToListKind[plural] = listGVK.Kind
60+
}
61+
62+
for gvr, listKind := range gvrToListKind {
63+
if !strings.HasSuffix(listKind, "List") {
64+
panic("coding error, listGVK must end in List or this fake client doesn't work right")
65+
}
66+
listGVK := gvr.GroupVersion().WithKind(listKind)
67+
68+
// if we already have this type registered, just skip it
69+
if _, err := scheme.New(listGVK); err == nil {
70+
completeGVRToListKind[gvr] = listKind
71+
continue
72+
}
73+
74+
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
75+
completeGVRToListKind[gvr] = listKind
76+
}
4277

4378
codecs := serializer.NewCodecFactory(scheme)
4479
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
@@ -48,7 +83,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
4883
}
4984
}
5085

51-
cs := &FakeDynamicClient{scheme: scheme}
86+
cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind}
5287
cs.AddReactor("*", "*", testing.ObjectReaction(o))
5388
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
5489
gvr := action.GetResource()
@@ -68,19 +103,21 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
68103
// you want to test easier.
69104
type FakeDynamicClient struct {
70105
testing.Fake
71-
scheme *runtime.Scheme
106+
scheme *runtime.Scheme
107+
gvrToListKind map[schema.GroupVersionResource]string
72108
}
73109

74110
type dynamicResourceClient struct {
75111
client *FakeDynamicClient
76112
namespace string
77113
resource schema.GroupVersionResource
114+
listKind string
78115
}
79116

80117
var _ dynamic.Interface = &FakeDynamicClient{}
81118

82119
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
83-
return &dynamicResourceClient{client: c, resource: resource}
120+
return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
84121
}
85122

86123
func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
@@ -276,16 +313,22 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
276313
}
277314

278315
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
316+
if len(c.listKind) == 0 {
317+
panic(fmt.Sprintf("coding error: you must register resource to list kind for every resource you're going to LIST when creating the client. See NewSimpleDynamicClientWithCustomListKinds or register the list into the scheme: %v out of %v", c.resource, c.client.gvrToListKind))
318+
}
319+
listGVK := c.resource.GroupVersion().WithKind(c.listKind)
320+
listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/
321+
279322
var obj runtime.Object
280323
var err error
281324
switch {
282325
case len(c.namespace) == 0:
283326
obj, err = c.client.Fake.
284-
Invokes(testing.NewRootListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, opts), &metav1.Status{Status: "dynamic list fail"})
327+
Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
285328

286329
case len(c.namespace) > 0:
287330
obj, err = c.client.Fake.
288-
Invokes(testing.NewListAction(c.resource, schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "" /*List is appended by the tracker automatically*/}, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
331+
Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
289332

290333
}
291334

@@ -309,6 +352,7 @@ func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOption
309352

310353
list := &unstructured.UnstructuredList{}
311354
list.SetResourceVersion(entireList.GetResourceVersion())
355+
list.GetObjectKind().SetGroupVersionKind(listGVK)
312356
for i := range entireList.Items {
313357
item := &entireList.Items[i]
314358
metadata, err := meta.Accessor(item)

dynamic/fake/simple_test.go

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,74 @@ func newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstruct
5959
return u
6060
}
6161

62+
func TestGet(t *testing.T) {
63+
scheme := runtime.NewScheme()
64+
65+
client := NewSimpleDynamicClient(scheme, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"))
66+
get, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).Namespace("ns-foo").Get(context.TODO(), "name-foo", metav1.GetOptions{})
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
expected := &unstructured.Unstructured{
72+
Object: map[string]interface{}{
73+
"apiVersion": "group/version",
74+
"kind": "TheKind",
75+
"metadata": map[string]interface{}{
76+
"name": "name-foo",
77+
"namespace": "ns-foo",
78+
},
79+
},
80+
}
81+
if !equality.Semantic.DeepEqual(get, expected) {
82+
t.Fatal(diff.ObjectGoPrintDiff(expected, get))
83+
}
84+
}
85+
86+
func TestListDecoding(t *testing.T) {
87+
// this the duplication of logic from the real List API. This will prove that our dynamic client actually returns the gvk
88+
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKindList", "items":[]}`))
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
list := uncastObj.(*unstructured.UnstructuredList)
93+
expectedList := &unstructured.UnstructuredList{
94+
Object: map[string]interface{}{
95+
"apiVersion": "group/version",
96+
"kind": "TheKindList",
97+
},
98+
Items: []unstructured.Unstructured{},
99+
}
100+
if !equality.Semantic.DeepEqual(list, expectedList) {
101+
t.Fatal(diff.ObjectGoPrintDiff(expectedList, list))
102+
}
103+
}
104+
105+
func TestGetDecoding(t *testing.T) {
106+
// this the duplication of logic from the real Get API. This will prove that our dynamic client actually returns the gvk
107+
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKind"}`))
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
get := uncastObj.(*unstructured.Unstructured)
112+
expectedObj := &unstructured.Unstructured{
113+
Object: map[string]interface{}{
114+
"apiVersion": "group/version",
115+
"kind": "TheKind",
116+
},
117+
}
118+
if !equality.Semantic.DeepEqual(get, expectedObj) {
119+
t.Fatal(diff.ObjectGoPrintDiff(expectedObj, get))
120+
}
121+
}
122+
62123
func TestList(t *testing.T) {
63124
scheme := runtime.NewScheme()
64125

65-
client := NewSimpleDynamicClient(scheme,
126+
client := NewSimpleDynamicClientWithCustomListKinds(scheme,
127+
map[schema.GroupVersionResource]string{
128+
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
129+
},
66130
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
67131
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
68132
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
@@ -87,7 +151,10 @@ func TestList(t *testing.T) {
87151
func Test_ListKind(t *testing.T) {
88152
scheme := runtime.NewScheme()
89153

90-
client := NewSimpleDynamicClient(scheme,
154+
client := NewSimpleDynamicClientWithCustomListKinds(scheme,
155+
map[schema.GroupVersionResource]string{
156+
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
157+
},
91158
&unstructured.UnstructuredList{
92159
Object: map[string]interface{}{
93160
"apiVersion": "group/version",

0 commit comments

Comments
 (0)