mirror of
https://github.com/kubernetes/client-go.git
synced 2025-07-04 18:56:21 +00:00
Simplify use of the fake dynamic client
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
This commit is contained in:
parent
f0bc45ffb4
commit
c6c0ca0e1e
@ -35,7 +35,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
|
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
|
// NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
|
||||||
@ -425,3 +453,41 @@ func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types
|
|||||||
}
|
}
|
||||||
return ret, err
|
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
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"k8s.io/apimachinery/pkg/api/equality"
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
@ -303,3 +304,166 @@ func TestPatch(t *testing.T) {
|
|||||||
t.Run(tc.name, tc.runner)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user