mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #96020 from deads2k/dynamiclist
update fake dynamic client to return GVK
This commit is contained in:
commit
dbd2be08fb
@ -92,39 +92,6 @@ func NewClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInternalClientNegotiator applies the default client rules for connecting to a Kubernetes apiserver
|
|
||||||
// where objects are converted to gv prior to sending and decoded to their internal representation prior
|
|
||||||
// to retrieval.
|
|
||||||
//
|
|
||||||
// DEPRECATED: Internal clients are deprecated and will be removed in a future Kubernetes release.
|
|
||||||
func NewInternalClientNegotiator(serializer NegotiatedSerializer, gv schema.GroupVersion) ClientNegotiator {
|
|
||||||
decode := schema.GroupVersions{
|
|
||||||
{
|
|
||||||
Group: gv.Group,
|
|
||||||
Version: APIVersionInternal,
|
|
||||||
},
|
|
||||||
// always include the legacy group as a decoding target to handle non-error `Status` return types
|
|
||||||
{
|
|
||||||
Group: "",
|
|
||||||
Version: APIVersionInternal,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &clientNegotiator{
|
|
||||||
encode: gv,
|
|
||||||
decode: decode,
|
|
||||||
serializer: serializer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSimpleClientNegotiator will negotiate for a single serializer. This should only be used
|
|
||||||
// for testing or when the caller is taking responsibility for setting the GVK on encoded objects.
|
|
||||||
func NewSimpleClientNegotiator(info SerializerInfo, gv schema.GroupVersion) ClientNegotiator {
|
|
||||||
return &clientNegotiator{
|
|
||||||
serializer: &simpleNegotiatedSerializer{info: info},
|
|
||||||
encode: gv,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleNegotiatedSerializer struct {
|
type simpleNegotiatedSerializer struct {
|
||||||
info SerializerInfo
|
info SerializerInfo
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,12 @@ func TestFilteredDynamicSharedInformerFactory(t *testing.T) {
|
|||||||
if ts.existingObj != nil {
|
if ts.existingObj != nil {
|
||||||
objs = append(objs, ts.existingObj)
|
objs = append(objs, ts.existingObj)
|
||||||
}
|
}
|
||||||
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
|
// don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
|
||||||
|
// a client that doesn't have a type registered in the scheme.
|
||||||
|
gvrToListKind := map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "apps", Version: "v1", Resource: "deployments"}: "DeploymentList",
|
||||||
|
}
|
||||||
|
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
|
||||||
target := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeClient, 0, ts.informNS, nil)
|
target := dynamicinformer.NewFilteredDynamicSharedInformerFactory(fakeClient, 0, ts.informNS, nil)
|
||||||
|
|
||||||
// act
|
// act
|
||||||
@ -214,7 +219,12 @@ func TestDynamicSharedInformerFactory(t *testing.T) {
|
|||||||
if ts.existingObj != nil {
|
if ts.existingObj != nil {
|
||||||
objs = append(objs, ts.existingObj)
|
objs = append(objs, ts.existingObj)
|
||||||
}
|
}
|
||||||
fakeClient := fake.NewSimpleDynamicClient(scheme, objs...)
|
// don't adjust the scheme to include deploymentlist. This is testing whether an informer can be created against using
|
||||||
|
// a client that doesn't have a type registered in the scheme.
|
||||||
|
gvrToListKind := map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "extensions", Version: "v1beta1", Resource: "deployments"}: "DeploymentList",
|
||||||
|
}
|
||||||
|
fakeClient := fake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objs...)
|
||||||
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
|
target := dynamicinformer.NewDynamicSharedInformerFactory(fakeClient, 0)
|
||||||
|
|
||||||
// act
|
// act
|
||||||
|
@ -18,6 +18,7 @@ package fake
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@ -34,11 +35,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
|
func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
|
||||||
// In order to use List with this client, you have to have the v1.List registered in your scheme. Neat thing though
|
return NewSimpleDynamicClientWithCustomListKinds(scheme, nil, objects...)
|
||||||
// it does NOT have to be the *same* list. UnstructuredList returned from this fake client will NOT have apiVersion and kind set,
|
}
|
||||||
// but each Unstructured object in Items will preserve their respective apiVersion and kind. As a result, schema conversion for
|
|
||||||
// *List kinds will not work and conversion of each Unstructured object in Items will be required instead.
|
// NewSimpleDynamicClientWithCustomListKinds try not to use this. In general you want to have the scheme have the List types registered
|
||||||
scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "fake-dynamic-client-group", Version: "v1", Kind: "List"}, &unstructured.UnstructuredList{})
|
// and allow the default guessing for resources match. Sometimes that doesn't work, so you can specify a custom mapping here.
|
||||||
|
func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
|
||||||
|
// In order to use List with this client, you have to have your lists registered so that the object tracker will find them
|
||||||
|
// in the scheme to support the t.scheme.New(listGVK) call when it's building the return value.
|
||||||
|
// Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it
|
||||||
|
// cannot look up the actual hits), we need to know a mapping of GVR to listGVK here. For GETs and other types of calls,
|
||||||
|
// there is no return value that contains a GVK, so it doesn't have to know the mapping in advance.
|
||||||
|
|
||||||
|
// first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses
|
||||||
|
// this covers common usage of registering types in scheme and passing them
|
||||||
|
completeGVRToListKind := map[schema.GroupVersionResource]string{}
|
||||||
|
for listGVK := range scheme.AllKnownTypes() {
|
||||||
|
if !strings.HasSuffix(listGVK.Kind, "List") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
|
||||||
|
plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
|
||||||
|
completeGVRToListKind[plural] = listGVK.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
for gvr, listKind := range gvrToListKind {
|
||||||
|
if !strings.HasSuffix(listKind, "List") {
|
||||||
|
panic("coding error, listGVK must end in List or this fake client doesn't work right")
|
||||||
|
}
|
||||||
|
listGVK := gvr.GroupVersion().WithKind(listKind)
|
||||||
|
|
||||||
|
// if we already have this type registered, just skip it
|
||||||
|
if _, err := scheme.New(listGVK); err == nil {
|
||||||
|
completeGVRToListKind[gvr] = listKind
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
|
||||||
|
completeGVRToListKind[gvr] = listKind
|
||||||
|
}
|
||||||
|
|
||||||
codecs := serializer.NewCodecFactory(scheme)
|
codecs := serializer.NewCodecFactory(scheme)
|
||||||
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
|
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
|
||||||
@ -48,7 +83,7 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := &FakeDynamicClient{scheme: scheme}
|
cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind}
|
||||||
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||||
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||||
gvr := action.GetResource()
|
gvr := action.GetResource()
|
||||||
@ -68,19 +103,21 @@ func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *
|
|||||||
// you want to test easier.
|
// you want to test easier.
|
||||||
type FakeDynamicClient struct {
|
type FakeDynamicClient struct {
|
||||||
testing.Fake
|
testing.Fake
|
||||||
scheme *runtime.Scheme
|
scheme *runtime.Scheme
|
||||||
|
gvrToListKind map[schema.GroupVersionResource]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type dynamicResourceClient struct {
|
type dynamicResourceClient struct {
|
||||||
client *FakeDynamicClient
|
client *FakeDynamicClient
|
||||||
namespace string
|
namespace string
|
||||||
resource schema.GroupVersionResource
|
resource schema.GroupVersionResource
|
||||||
|
listKind string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ dynamic.Interface = &FakeDynamicClient{}
|
var _ dynamic.Interface = &FakeDynamicClient{}
|
||||||
|
|
||||||
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
|
func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
|
||||||
return &dynamicResourceClient{client: c, resource: resource}
|
return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
|
func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
|
||||||
@ -276,16 +313,22 @@ func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
||||||
|
if len(c.listKind) == 0 {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
listGVK := c.resource.GroupVersion().WithKind(c.listKind)
|
||||||
|
listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/
|
||||||
|
|
||||||
var obj runtime.Object
|
var obj runtime.Object
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch {
|
||||||
case len(c.namespace) == 0:
|
case len(c.namespace) == 0:
|
||||||
obj, err = c.client.Fake.
|
obj, err = c.client.Fake.
|
||||||
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"})
|
Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
|
||||||
|
|
||||||
case len(c.namespace) > 0:
|
case len(c.namespace) > 0:
|
||||||
obj, err = c.client.Fake.
|
obj, err = c.client.Fake.
|
||||||
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"})
|
Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,6 +352,7 @@ func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOption
|
|||||||
|
|
||||||
list := &unstructured.UnstructuredList{}
|
list := &unstructured.UnstructuredList{}
|
||||||
list.SetResourceVersion(entireList.GetResourceVersion())
|
list.SetResourceVersion(entireList.GetResourceVersion())
|
||||||
|
list.GetObjectKind().SetGroupVersionKind(listGVK)
|
||||||
for i := range entireList.Items {
|
for i := range entireList.Items {
|
||||||
item := &entireList.Items[i]
|
item := &entireList.Items[i]
|
||||||
metadata, err := meta.Accessor(item)
|
metadata, err := meta.Accessor(item)
|
||||||
|
@ -59,10 +59,74 @@ func newUnstructuredWithSpec(spec map[string]interface{}) *unstructured.Unstruct
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
|
||||||
|
client := NewSimpleDynamicClient(scheme, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"))
|
||||||
|
get, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).Namespace("ns-foo").Get(context.TODO(), "name-foo", metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "group/version",
|
||||||
|
"kind": "TheKind",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "name-foo",
|
||||||
|
"namespace": "ns-foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(get, expected) {
|
||||||
|
t.Fatal(diff.ObjectGoPrintDiff(expected, get))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListDecoding(t *testing.T) {
|
||||||
|
// this the duplication of logic from the real List API. This will prove that our dynamic client actually returns the gvk
|
||||||
|
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKindList", "items":[]}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
list := uncastObj.(*unstructured.UnstructuredList)
|
||||||
|
expectedList := &unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "group/version",
|
||||||
|
"kind": "TheKindList",
|
||||||
|
},
|
||||||
|
Items: []unstructured.Unstructured{},
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(list, expectedList) {
|
||||||
|
t.Fatal(diff.ObjectGoPrintDiff(expectedList, list))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDecoding(t *testing.T) {
|
||||||
|
// this the duplication of logic from the real Get API. This will prove that our dynamic client actually returns the gvk
|
||||||
|
uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, []byte(`{"apiVersion": "group/version", "kind": "TheKind"}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
get := uncastObj.(*unstructured.Unstructured)
|
||||||
|
expectedObj := &unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "group/version",
|
||||||
|
"kind": "TheKind",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(get, expectedObj) {
|
||||||
|
t.Fatal(diff.ObjectGoPrintDiff(expectedObj, get))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
|
|
||||||
client := NewSimpleDynamicClient(scheme,
|
client := NewSimpleDynamicClientWithCustomListKinds(scheme,
|
||||||
|
map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
|
||||||
|
},
|
||||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
|
newUnstructured("group2/version", "TheKind", "ns-foo", "name2-foo"),
|
||||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
@ -84,6 +148,49 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_ListKind(t *testing.T) {
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
|
||||||
|
client := NewSimpleDynamicClientWithCustomListKinds(scheme,
|
||||||
|
map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "group", Version: "version", Resource: "thekinds"}: "TheKindList",
|
||||||
|
},
|
||||||
|
&unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "group/version",
|
||||||
|
"kind": "TheKindList",
|
||||||
|
},
|
||||||
|
Items: []unstructured.Unstructured{
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
listFirst, err := client.Resource(schema.GroupVersionResource{Group: "group", Version: "version", Resource: "thekinds"}).List(context.TODO(), metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedList := &unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "group/version",
|
||||||
|
"kind": "TheKindList",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"resourceVersion": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Items: []unstructured.Unstructured{
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-bar"),
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-baz"),
|
||||||
|
*newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !equality.Semantic.DeepEqual(listFirst, expectedList) {
|
||||||
|
t.Fatal(diff.ObjectGoPrintDiff(expectedList, listFirst))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type patchTestCase struct {
|
type patchTestCase struct {
|
||||||
name string
|
name string
|
||||||
object runtime.Object
|
object runtime.Object
|
||||||
|
@ -2,11 +2,18 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test")
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = ["timeout_test.go"],
|
srcs = [
|
||||||
|
"fake_client_test.go",
|
||||||
|
"timeout_test.go",
|
||||||
|
],
|
||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
|
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This test proves that the kube fake client does not return GVKs. This is consistent with actual client (see tests below)
|
||||||
|
// and should not be changed unless the decoding behavior and somehow literal creation (`&corev1.ConfigMap{}`) behavior change.
|
||||||
|
func Test_ConfigMapFakeClient(t *testing.T) {
|
||||||
|
fakeKubeClient := fake.NewSimpleClientset(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: "foo-ns", Name: "foo-name"}})
|
||||||
|
cm, err := fakeKubeClient.CoreV1().ConfigMaps("foo-ns").Get(context.TODO(), "foo-name", metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cm.GetObjectKind().GroupVersionKind() != (schema.GroupVersionKind{}) {
|
||||||
|
t.Fatal(cm.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
cmList, err := fakeKubeClient.CoreV1().ConfigMaps("foo-ns").List(context.TODO(), metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cmList.GetObjectKind().GroupVersionKind() != (schema.GroupVersionKind{}) {
|
||||||
|
t.Fatal(cmList.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks decoding behavior for the actual client to ensure the fake client (tested above) is consistent.
|
||||||
|
func TestGetDecoding(t *testing.T) {
|
||||||
|
// this the duplication of logic from the real Get API for configmaps. This will prove that the generated client will not return a GVK
|
||||||
|
mediaTypes := scheme.Codecs.WithoutConversion().SupportedMediaTypes()
|
||||||
|
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, "application/json")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing serializer")
|
||||||
|
}
|
||||||
|
decoder := scheme.Codecs.WithoutConversion().DecoderToVersion(info.Serializer, corev1.SchemeGroupVersion)
|
||||||
|
|
||||||
|
body := []byte(`{"apiVersion": "v1", "kind": "ConfigMap", "metadata":{"Namespace":"foo","Name":"bar"}}`)
|
||||||
|
obj := &corev1.ConfigMap{}
|
||||||
|
out, _, err := decoder.Decode(body, nil, obj)
|
||||||
|
if err != nil || out != obj {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.GetObjectKind().GroupVersionKind() != (schema.GroupVersionKind{}) {
|
||||||
|
t.Fatal(obj.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks decoding behavior for the actual client to ensure the fake client (tested above) is consistent.
|
||||||
|
func TestListDecoding(t *testing.T) {
|
||||||
|
// this the duplication of logic from the real Get API for configmaps. This will prove that the generated client will not return a GVK
|
||||||
|
mediaTypes := scheme.Codecs.WithoutConversion().SupportedMediaTypes()
|
||||||
|
info, ok := runtime.SerializerInfoForMediaType(mediaTypes, "application/json")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("missing serializer")
|
||||||
|
}
|
||||||
|
decoder := scheme.Codecs.WithoutConversion().DecoderToVersion(info.Serializer, corev1.SchemeGroupVersion)
|
||||||
|
|
||||||
|
body := []byte(`{"apiVersion": "v1", "kind": "ConfigMapList", "items":[]}`)
|
||||||
|
obj := &corev1.ConfigMapList{}
|
||||||
|
out, _, err := decoder.Decode(body, nil, obj)
|
||||||
|
if err != nil || out != obj {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.GetObjectKind().GroupVersionKind() != (schema.GroupVersionKind{}) {
|
||||||
|
t.Fatal(obj.GetObjectKind().GroupVersionKind())
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,11 @@ func addCondition(in *unstructured.Unstructured, name, status string) *unstructu
|
|||||||
|
|
||||||
func TestWaitForDeletion(t *testing.T) {
|
func TestWaitForDeletion(t *testing.T) {
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
|
listMapping := map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
|
||||||
|
{Group: "group", Version: "version", Resource: "theresource-1"}: "TheKindList",
|
||||||
|
{Group: "group", Version: "version", Resource: "theresource-2"}: "TheKindList",
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -105,7 +110,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
return dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
return dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
},
|
},
|
||||||
timeout: 10 * time.Second,
|
timeout: 10 * time.Second,
|
||||||
|
|
||||||
@ -145,7 +150,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -192,7 +197,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -225,7 +230,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
|
unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
|
||||||
unstructuredObj.SetResourceVersion("123")
|
unstructuredObj.SetResourceVersion("123")
|
||||||
@ -282,7 +287,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -326,7 +331,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("get", "theresource-1", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("get", "theresource-1", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-1"), nil
|
return true, newUnstructured("group/version", "TheKind", "ns-foo", "name-foo-1"), nil
|
||||||
})
|
})
|
||||||
@ -371,7 +376,7 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -449,6 +454,9 @@ func TestWaitForDeletion(t *testing.T) {
|
|||||||
|
|
||||||
func TestWaitForCondition(t *testing.T) {
|
func TestWaitForCondition(t *testing.T) {
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
|
listMapping := map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -471,7 +479,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(addCondition(
|
return true, newUnstructuredList(addCondition(
|
||||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
@ -517,7 +525,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
return dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
return dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
},
|
},
|
||||||
timeout: 10 * time.Second,
|
timeout: 10 * time.Second,
|
||||||
expectedErr: "resource name must be provided",
|
expectedErr: "resource name must be provided",
|
||||||
@ -540,7 +548,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, addCondition(
|
return true, addCondition(
|
||||||
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
newUnstructured("group/version", "TheKind", "ns-foo", "name-foo"),
|
||||||
@ -576,7 +584,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
|
unstructuredObj := newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")
|
||||||
unstructuredObj.SetResourceVersion("123")
|
unstructuredObj.SetResourceVersion("123")
|
||||||
@ -633,7 +641,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -673,7 +681,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
|
fakeClient.PrependWatchReactor("theresource", func(action clienttesting.Action) (handled bool, ret watch.Interface, err error) {
|
||||||
fakeWatch := watch.NewRaceFreeFake()
|
fakeWatch := watch.NewRaceFreeFake()
|
||||||
fakeWatch.Action(watch.Added, addCondition(
|
fakeWatch.Action(watch.Added, addCondition(
|
||||||
@ -710,7 +718,7 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
fakeClient: func() *dynamicfakeclient.FakeDynamicClient {
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
fakeClient.PrependReactor("list", "theresource", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||||
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
return true, newUnstructuredList(newUnstructured("group/version", "TheKind", "ns-foo", "name-foo")), nil
|
||||||
})
|
})
|
||||||
@ -790,6 +798,9 @@ func TestWaitForCondition(t *testing.T) {
|
|||||||
|
|
||||||
func TestWaitForDeletionIgnoreNotFound(t *testing.T) {
|
func TestWaitForDeletionIgnoreNotFound(t *testing.T) {
|
||||||
scheme := runtime.NewScheme()
|
scheme := runtime.NewScheme()
|
||||||
|
listMapping := map[schema.GroupVersionResource]string{
|
||||||
|
{Group: "group", Version: "version", Resource: "theresource"}: "TheKindList",
|
||||||
|
}
|
||||||
infos := []*resource.Info{
|
infos := []*resource.Info{
|
||||||
{
|
{
|
||||||
Mapping: &meta.RESTMapping{
|
Mapping: &meta.RESTMapping{
|
||||||
@ -799,7 +810,7 @@ func TestWaitForDeletionIgnoreNotFound(t *testing.T) {
|
|||||||
Namespace: "ns-foo",
|
Namespace: "ns-foo",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fakeClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeClient := dynamicfakeclient.NewSimpleDynamicClientWithCustomListKinds(scheme, listMapping)
|
||||||
|
|
||||||
o := &WaitOptions{
|
o := &WaitOptions{
|
||||||
ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(infos...),
|
ResourceFinder: genericclioptions.NewSimpleFakeResourceFinder(infos...),
|
||||||
|
Loading…
Reference in New Issue
Block a user