Skip to content

Commit

Permalink
Simplify use of the fake dynamic client
Browse files Browse the repository at this point in the history
With the introduction of GVK to the fake dynamic client it made using
the fake much more cumbersome.

Specifically:
- requires manual registration of list types
- mismatch between scheme types and passed in fixtures would result in errors

The PR changes the constructor method NewSimpleDynamicClient to do the following:
- rewire the schemes to unstructured types
- typed fixtures are converted to unstructured types
- automatically register fixture gvks with the scheme

This should make the dynamic client 'flexible' with it's inputs like it was
before

Kubernetes-commit: 418fa71b6b1d1fba930daaba1f8ecf55070b4bdf
  • Loading branch information
dprotaso authored and k8s-publishing-bot committed Jun 16, 2021
1 parent f0bc45f commit c6c0ca0
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 1 deletion.
68 changes: 67 additions & 1 deletion dynamic/fake/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,35 @@ import (
)

func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
return NewSimpleDynamicClientWithCustomListKinds(scheme, nil, objects...)
unstructuredScheme := runtime.NewScheme()
for gvk := range scheme.AllKnownTypes() {
if unstructuredScheme.Recognizes(gvk) {
continue
}
if strings.HasSuffix(gvk.Kind, "List") {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
continue
}
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
}

objects, err := convertObjectsToUnstructured(scheme, objects)
if err != nil {
panic(err)
}

for _, obj := range objects {
gvk := obj.GetObjectKind().GroupVersionKind()
if !unstructuredScheme.Recognizes(gvk) {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
}
gvk.Kind += "List"
if !unstructuredScheme.Recognizes(gvk) {
unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
}
}

return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
}

// NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
Expand Down Expand Up @@ -425,3 +453,41 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
}
return ret, err
}

func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
ul := make([]runtime.Object, 0, len(objs))

for _, obj := range objs {
u, err := convertToUnstructured(s, obj)
if err != nil {
return nil, err
}

ul = append(ul, u)
}
return ul, nil
}

func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) {
var (
err error
u unstructured.Unstructured
)

u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured: %w", err)
}

gvk := u.GroupVersionKind()
if gvk.Group == "" || gvk.Kind == "" {
gvks, _, err := s.ObjectKinds(obj)
if err != nil {
return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
}
apiv, k := gvks[0].ToAPIVersionAndKind()
u.SetAPIVersion(apiv)
u.SetKind(k)
}
return &u, nil
}
164 changes: 164 additions & 0 deletions dynamic/fake/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -303,3 +304,166 @@ func TestPatch(t *testing.T) {
t.Run(tc.name, tc.runner)
}
}

// This test ensures list works when the fake dynamic client is seeded with a typed scheme and
// unstructured type fixtures
func TestListWithUnstructuredObjectsAndTypedScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)

listGVK := gvk
listGVK.Kind += "List"

u := unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
u.SetName("name")
u.SetNamespace("namespace")

typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})

client := NewSimpleDynamicClient(typedScheme, &u)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})

if err != nil {
t.Error("error listing", err)
}

expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = append(expectedList.Items, u)

if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}

func TestListWithNoFixturesAndTypedScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)

listGVK := gvk
listGVK.Kind += "List"

typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})

client := NewSimpleDynamicClient(typedScheme)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})

if err != nil {
t.Error("error listing", err)
}

expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version

if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}

// This test ensures list works when the dynamic client is seeded with an empty scheme and
// unstructured typed fixtures
func TestListWithNoScheme(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)

listGVK := gvk
listGVK.Kind += "List"

u := unstructured.Unstructured{}
u.SetGroupVersionKind(gvk)
u.SetName("name")
u.SetNamespace("namespace")

emptyScheme := runtime.NewScheme()

client := NewSimpleDynamicClient(emptyScheme, &u)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})

if err != nil {
t.Error("error listing", err)
}

expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = append(expectedList.Items, u)

if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}

// This test ensures list works when the dynamic client is seeded with an empty scheme and
// unstructured typed fixtures
func TestListWithTypedFixtures(t *testing.T) {
gvr := schema.GroupVersionResource{Group: testGroup, Version: testVersion, Resource: testResource}
gvk := gvr.GroupVersion().WithKind(testKind)

listGVK := gvk
listGVK.Kind += "List"

r := mockResource{}
r.SetGroupVersionKind(gvk)
r.SetName("name")
r.SetNamespace("namespace")

u := unstructured.Unstructured{}
u.SetGroupVersionKind(r.GetObjectKind().GroupVersionKind())
u.SetName(r.GetName())
u.SetNamespace(r.GetNamespace())
// Needed see: https://github.com/kubernetes/kubernetes/issues/67610
unstructured.SetNestedField(u.Object, nil, "metadata", "creationTimestamp")

typedScheme := runtime.NewScheme()
typedScheme.AddKnownTypeWithName(gvk, &mockResource{})
typedScheme.AddKnownTypeWithName(listGVK, &mockResourceList{})

client := NewSimpleDynamicClient(typedScheme, &r)
list, err := client.Resource(gvr).Namespace("namespace").List(context.Background(), metav1.ListOptions{})

if err != nil {
t.Error("error listing", err)
}

expectedList := &unstructured.UnstructuredList{}
expectedList.SetGroupVersionKind(listGVK)
expectedList.SetResourceVersion("") // by product of the fake setting resource version
expectedList.Items = []unstructured.Unstructured{u}

if diff := cmp.Diff(expectedList, list); diff != "" {
t.Fatal("unexpected diff (-want, +got): ", diff)
}
}

type (
mockResource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
}
mockResourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`

Items []mockResource
}
)

func (l *mockResourceList) DeepCopyObject() runtime.Object {
o := *l
return &o
}

func (r *mockResource) DeepCopyObject() runtime.Object {
o := *r
return &o
}

var _ runtime.Object = (*mockResource)(nil)
var _ runtime.Object = (*mockResourceList)(nil)

0 comments on commit c6c0ca0

Please sign in to comment.