Skip to content

Commit

Permalink
feat: Add fakeutil which addresses kubernetes/client-go#914
Browse files Browse the repository at this point in the history
  • Loading branch information
timonwong committed Jul 29, 2021
1 parent 1a2d1f7 commit 2b4a81b
Show file tree
Hide file tree
Showing 4 changed files with 456 additions and 135 deletions.
81 changes: 81 additions & 0 deletions fakeutil/simple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package fakeutil

import (
"fmt"
"strings"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
)

// NewSimpleDynamicClient creates fake dynamic client which addresses kubernetes/client-go#914
// Ported from this PR: https://github.com/kubernetes/kubernetes/pull/102928
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *fake.FakeDynamicClient {
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 fake.NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
}

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)
}

u.SetGroupVersionKind(gvks[0])
}
return &u, nil
}
166 changes: 166 additions & 0 deletions fakeutil/simple_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package fakeutil

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

const (
testGroup = "testgroup"
testVersion = "testversion"
testResource = "testkinds"
testNamespace = "testns"
testName = "testname"
testKind = "TestKind"
testAPIVersion = "testgroup/testversion"
)

// 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{})
require.NoError(t, err, "error listing")

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

assert.Equal(t, expectedList, list)
}

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{})
require.NoError(t, err, "error listing")

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

assert.Equal(t, expectedList, list)
}

// 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{})
require.NoError(t, err, "error listing")

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

assert.Equal(t, expectedList, list)
}

// 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{})
require.NoError(t, err, "error listing")

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

assert.Equal(t, expectedList, list)
}

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)
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ go 1.15

require (
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.13.0
k8s.io/api v0.19.2
k8s.io/apimachinery v0.19.2
k8s.io/client-go v0.19.2
sigs.k8s.io/controller-runtime v0.7.2
github.com/onsi/gomega v1.14.0
github.com/stretchr/testify v1.6.1
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
k8s.io/api v0.20.8
k8s.io/apimachinery v0.20.8
k8s.io/client-go v0.20.8
sigs.k8s.io/controller-runtime v0.8.3
)
Loading

0 comments on commit 2b4a81b

Please sign in to comment.