k8s.io/client-go@v0.22.2/dynamic/fake/simple.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package fake
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/runtime/serializer"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/apimachinery/pkg/watch"
    33  	"k8s.io/client-go/dynamic"
    34  	"k8s.io/client-go/testing"
    35  )
    36  
    37  func NewSimpleDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) *FakeDynamicClient {
    38  	unstructuredScheme := runtime.NewScheme()
    39  	for gvk := range scheme.AllKnownTypes() {
    40  		if unstructuredScheme.Recognizes(gvk) {
    41  			continue
    42  		}
    43  		if strings.HasSuffix(gvk.Kind, "List") {
    44  			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
    45  			continue
    46  		}
    47  		unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
    48  	}
    49  
    50  	objects, err := convertObjectsToUnstructured(scheme, objects)
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  
    55  	for _, obj := range objects {
    56  		gvk := obj.GetObjectKind().GroupVersionKind()
    57  		if !unstructuredScheme.Recognizes(gvk) {
    58  			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
    59  		}
    60  		gvk.Kind += "List"
    61  		if !unstructuredScheme.Recognizes(gvk) {
    62  			unstructuredScheme.AddKnownTypeWithName(gvk, &unstructured.UnstructuredList{})
    63  		}
    64  	}
    65  
    66  	return NewSimpleDynamicClientWithCustomListKinds(unstructuredScheme, nil, objects...)
    67  }
    68  
    69  // NewSimpleDynamicClientWithCustomListKinds try not to use this.  In general you want to have the scheme have the List types registered
    70  // and allow the default guessing for resources match.  Sometimes that doesn't work, so you can specify a custom mapping here.
    71  func NewSimpleDynamicClientWithCustomListKinds(scheme *runtime.Scheme, gvrToListKind map[schema.GroupVersionResource]string, objects ...runtime.Object) *FakeDynamicClient {
    72  	// In order to use List with this client, you have to have your lists registered so that the object tracker will find them
    73  	// in the scheme to support the t.scheme.New(listGVK) call when it's building the return value.
    74  	// Since the base fake client needs the listGVK passed through the action (in cases where there are no instances, it
    75  	// cannot look up the actual hits), we need to know a mapping of GVR to listGVK here.  For GETs and other types of calls,
    76  	// there is no return value that contains a GVK, so it doesn't have to know the mapping in advance.
    77  
    78  	// first we attempt to invert known List types from the scheme to auto guess the resource with unsafe guesses
    79  	// this covers common usage of registering types in scheme and passing them
    80  	completeGVRToListKind := map[schema.GroupVersionResource]string{}
    81  	for listGVK := range scheme.AllKnownTypes() {
    82  		if !strings.HasSuffix(listGVK.Kind, "List") {
    83  			continue
    84  		}
    85  		nonListGVK := listGVK.GroupVersion().WithKind(listGVK.Kind[:len(listGVK.Kind)-4])
    86  		plural, _ := meta.UnsafeGuessKindToResource(nonListGVK)
    87  		completeGVRToListKind[plural] = listGVK.Kind
    88  	}
    89  
    90  	for gvr, listKind := range gvrToListKind {
    91  		if !strings.HasSuffix(listKind, "List") {
    92  			panic("coding error, listGVK must end in List or this fake client doesn't work right")
    93  		}
    94  		listGVK := gvr.GroupVersion().WithKind(listKind)
    95  
    96  		// if we already have this type registered, just skip it
    97  		if _, err := scheme.New(listGVK); err == nil {
    98  			completeGVRToListKind[gvr] = listKind
    99  			continue
   100  		}
   101  
   102  		scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
   103  		completeGVRToListKind[gvr] = listKind
   104  	}
   105  
   106  	codecs := serializer.NewCodecFactory(scheme)
   107  	o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
   108  	for _, obj := range objects {
   109  		if err := o.Add(obj); err != nil {
   110  			panic(err)
   111  		}
   112  	}
   113  
   114  	cs := &FakeDynamicClient{scheme: scheme, gvrToListKind: completeGVRToListKind, tracker: o}
   115  	cs.AddReactor("*", "*", testing.ObjectReaction(o))
   116  	cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
   117  		gvr := action.GetResource()
   118  		ns := action.GetNamespace()
   119  		watch, err := o.Watch(gvr, ns)
   120  		if err != nil {
   121  			return false, nil, err
   122  		}
   123  		return true, watch, nil
   124  	})
   125  
   126  	return cs
   127  }
   128  
   129  // Clientset implements clientset.Interface. Meant to be embedded into a
   130  // struct to get a default implementation. This makes faking out just the method
   131  // you want to test easier.
   132  type FakeDynamicClient struct {
   133  	testing.Fake
   134  	scheme        *runtime.Scheme
   135  	gvrToListKind map[schema.GroupVersionResource]string
   136  	tracker       testing.ObjectTracker
   137  }
   138  
   139  type dynamicResourceClient struct {
   140  	client    *FakeDynamicClient
   141  	namespace string
   142  	resource  schema.GroupVersionResource
   143  	listKind  string
   144  }
   145  
   146  var (
   147  	_ dynamic.Interface  = &FakeDynamicClient{}
   148  	_ testing.FakeClient = &FakeDynamicClient{}
   149  )
   150  
   151  func (c *FakeDynamicClient) Tracker() testing.ObjectTracker {
   152  	return c.tracker
   153  }
   154  
   155  func (c *FakeDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
   156  	return &dynamicResourceClient{client: c, resource: resource, listKind: c.gvrToListKind[resource]}
   157  }
   158  
   159  func (c *dynamicResourceClient) Namespace(ns string) dynamic.ResourceInterface {
   160  	ret := *c
   161  	ret.namespace = ns
   162  	return &ret
   163  }
   164  
   165  func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
   166  	var uncastRet runtime.Object
   167  	var err error
   168  	switch {
   169  	case len(c.namespace) == 0 && len(subresources) == 0:
   170  		uncastRet, err = c.client.Fake.
   171  			Invokes(testing.NewRootCreateAction(c.resource, obj), obj)
   172  
   173  	case len(c.namespace) == 0 && len(subresources) > 0:
   174  		var accessor metav1.Object // avoid shadowing err
   175  		accessor, err = meta.Accessor(obj)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  		name := accessor.GetName()
   180  		uncastRet, err = c.client.Fake.
   181  			Invokes(testing.NewRootCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), obj), obj)
   182  
   183  	case len(c.namespace) > 0 && len(subresources) == 0:
   184  		uncastRet, err = c.client.Fake.
   185  			Invokes(testing.NewCreateAction(c.resource, c.namespace, obj), obj)
   186  
   187  	case len(c.namespace) > 0 && len(subresources) > 0:
   188  		var accessor metav1.Object // avoid shadowing err
   189  		accessor, err = meta.Accessor(obj)
   190  		if err != nil {
   191  			return nil, err
   192  		}
   193  		name := accessor.GetName()
   194  		uncastRet, err = c.client.Fake.
   195  			Invokes(testing.NewCreateSubresourceAction(c.resource, name, strings.Join(subresources, "/"), c.namespace, obj), obj)
   196  
   197  	}
   198  
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	if uncastRet == nil {
   203  		return nil, err
   204  	}
   205  
   206  	ret := &unstructured.Unstructured{}
   207  	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
   208  		return nil, err
   209  	}
   210  	return ret, err
   211  }
   212  
   213  func (c *dynamicResourceClient) Update(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions, subresources ...string) (*unstructured.Unstructured, error) {
   214  	var uncastRet runtime.Object
   215  	var err error
   216  	switch {
   217  	case len(c.namespace) == 0 && len(subresources) == 0:
   218  		uncastRet, err = c.client.Fake.
   219  			Invokes(testing.NewRootUpdateAction(c.resource, obj), obj)
   220  
   221  	case len(c.namespace) == 0 && len(subresources) > 0:
   222  		uncastRet, err = c.client.Fake.
   223  			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), obj), obj)
   224  
   225  	case len(c.namespace) > 0 && len(subresources) == 0:
   226  		uncastRet, err = c.client.Fake.
   227  			Invokes(testing.NewUpdateAction(c.resource, c.namespace, obj), obj)
   228  
   229  	case len(c.namespace) > 0 && len(subresources) > 0:
   230  		uncastRet, err = c.client.Fake.
   231  			Invokes(testing.NewUpdateSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, obj), obj)
   232  
   233  	}
   234  
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	if uncastRet == nil {
   239  		return nil, err
   240  	}
   241  
   242  	ret := &unstructured.Unstructured{}
   243  	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
   244  		return nil, err
   245  	}
   246  	return ret, err
   247  }
   248  
   249  func (c *dynamicResourceClient) UpdateStatus(ctx context.Context, obj *unstructured.Unstructured, opts metav1.UpdateOptions) (*unstructured.Unstructured, error) {
   250  	var uncastRet runtime.Object
   251  	var err error
   252  	switch {
   253  	case len(c.namespace) == 0:
   254  		uncastRet, err = c.client.Fake.
   255  			Invokes(testing.NewRootUpdateSubresourceAction(c.resource, "status", obj), obj)
   256  
   257  	case len(c.namespace) > 0:
   258  		uncastRet, err = c.client.Fake.
   259  			Invokes(testing.NewUpdateSubresourceAction(c.resource, "status", c.namespace, obj), obj)
   260  
   261  	}
   262  
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  	if uncastRet == nil {
   267  		return nil, err
   268  	}
   269  
   270  	ret := &unstructured.Unstructured{}
   271  	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
   272  		return nil, err
   273  	}
   274  	return ret, err
   275  }
   276  
   277  func (c *dynamicResourceClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error {
   278  	var err error
   279  	switch {
   280  	case len(c.namespace) == 0 && len(subresources) == 0:
   281  		_, err = c.client.Fake.
   282  			Invokes(testing.NewRootDeleteAction(c.resource, name), &metav1.Status{Status: "dynamic delete fail"})
   283  
   284  	case len(c.namespace) == 0 && len(subresources) > 0:
   285  		_, err = c.client.Fake.
   286  			Invokes(testing.NewRootDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic delete fail"})
   287  
   288  	case len(c.namespace) > 0 && len(subresources) == 0:
   289  		_, err = c.client.Fake.
   290  			Invokes(testing.NewDeleteAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
   291  
   292  	case len(c.namespace) > 0 && len(subresources) > 0:
   293  		_, err = c.client.Fake.
   294  			Invokes(testing.NewDeleteSubresourceAction(c.resource, strings.Join(subresources, "/"), c.namespace, name), &metav1.Status{Status: "dynamic delete fail"})
   295  	}
   296  
   297  	return err
   298  }
   299  
   300  func (c *dynamicResourceClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOptions metav1.ListOptions) error {
   301  	var err error
   302  	switch {
   303  	case len(c.namespace) == 0:
   304  		action := testing.NewRootDeleteCollectionAction(c.resource, listOptions)
   305  		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
   306  
   307  	case len(c.namespace) > 0:
   308  		action := testing.NewDeleteCollectionAction(c.resource, c.namespace, listOptions)
   309  		_, err = c.client.Fake.Invokes(action, &metav1.Status{Status: "dynamic deletecollection fail"})
   310  
   311  	}
   312  
   313  	return err
   314  }
   315  
   316  func (c *dynamicResourceClient) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) {
   317  	var uncastRet runtime.Object
   318  	var err error
   319  	switch {
   320  	case len(c.namespace) == 0 && len(subresources) == 0:
   321  		uncastRet, err = c.client.Fake.
   322  			Invokes(testing.NewRootGetAction(c.resource, name), &metav1.Status{Status: "dynamic get fail"})
   323  
   324  	case len(c.namespace) == 0 && len(subresources) > 0:
   325  		uncastRet, err = c.client.Fake.
   326  			Invokes(testing.NewRootGetSubresourceAction(c.resource, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
   327  
   328  	case len(c.namespace) > 0 && len(subresources) == 0:
   329  		uncastRet, err = c.client.Fake.
   330  			Invokes(testing.NewGetAction(c.resource, c.namespace, name), &metav1.Status{Status: "dynamic get fail"})
   331  
   332  	case len(c.namespace) > 0 && len(subresources) > 0:
   333  		uncastRet, err = c.client.Fake.
   334  			Invokes(testing.NewGetSubresourceAction(c.resource, c.namespace, strings.Join(subresources, "/"), name), &metav1.Status{Status: "dynamic get fail"})
   335  	}
   336  
   337  	if err != nil {
   338  		return nil, err
   339  	}
   340  	if uncastRet == nil {
   341  		return nil, err
   342  	}
   343  
   344  	ret := &unstructured.Unstructured{}
   345  	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
   346  		return nil, err
   347  	}
   348  	return ret, err
   349  }
   350  
   351  func (c *dynamicResourceClient) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
   352  	if len(c.listKind) == 0 {
   353  		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))
   354  	}
   355  	listGVK := c.resource.GroupVersion().WithKind(c.listKind)
   356  	listForFakeClientGVK := c.resource.GroupVersion().WithKind(c.listKind[:len(c.listKind)-4]) /*base library appends List*/
   357  
   358  	var obj runtime.Object
   359  	var err error
   360  	switch {
   361  	case len(c.namespace) == 0:
   362  		obj, err = c.client.Fake.
   363  			Invokes(testing.NewRootListAction(c.resource, listForFakeClientGVK, opts), &metav1.Status{Status: "dynamic list fail"})
   364  
   365  	case len(c.namespace) > 0:
   366  		obj, err = c.client.Fake.
   367  			Invokes(testing.NewListAction(c.resource, listForFakeClientGVK, c.namespace, opts), &metav1.Status{Status: "dynamic list fail"})
   368  
   369  	}
   370  
   371  	if obj == nil {
   372  		return nil, err
   373  	}
   374  
   375  	label, _, _ := testing.ExtractFromListOptions(opts)
   376  	if label == nil {
   377  		label = labels.Everything()
   378  	}
   379  
   380  	retUnstructured := &unstructured.Unstructured{}
   381  	if err := c.client.scheme.Convert(obj, retUnstructured, nil); err != nil {
   382  		return nil, err
   383  	}
   384  	entireList, err := retUnstructured.ToList()
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	list := &unstructured.UnstructuredList{}
   390  	list.SetResourceVersion(entireList.GetResourceVersion())
   391  	list.GetObjectKind().SetGroupVersionKind(listGVK)
   392  	for i := range entireList.Items {
   393  		item := &entireList.Items[i]
   394  		metadata, err := meta.Accessor(item)
   395  		if err != nil {
   396  			return nil, err
   397  		}
   398  		if label.Matches(labels.Set(metadata.GetLabels())) {
   399  			list.Items = append(list.Items, *item)
   400  		}
   401  	}
   402  	return list, nil
   403  }
   404  
   405  func (c *dynamicResourceClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   406  	switch {
   407  	case len(c.namespace) == 0:
   408  		return c.client.Fake.
   409  			InvokesWatch(testing.NewRootWatchAction(c.resource, opts))
   410  
   411  	case len(c.namespace) > 0:
   412  		return c.client.Fake.
   413  			InvokesWatch(testing.NewWatchAction(c.resource, c.namespace, opts))
   414  
   415  	}
   416  
   417  	panic("math broke")
   418  }
   419  
   420  // TODO: opts are currently ignored.
   421  func (c *dynamicResourceClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*unstructured.Unstructured, error) {
   422  	var uncastRet runtime.Object
   423  	var err error
   424  	switch {
   425  	case len(c.namespace) == 0 && len(subresources) == 0:
   426  		uncastRet, err = c.client.Fake.
   427  			Invokes(testing.NewRootPatchAction(c.resource, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
   428  
   429  	case len(c.namespace) == 0 && len(subresources) > 0:
   430  		uncastRet, err = c.client.Fake.
   431  			Invokes(testing.NewRootPatchSubresourceAction(c.resource, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
   432  
   433  	case len(c.namespace) > 0 && len(subresources) == 0:
   434  		uncastRet, err = c.client.Fake.
   435  			Invokes(testing.NewPatchAction(c.resource, c.namespace, name, pt, data), &metav1.Status{Status: "dynamic patch fail"})
   436  
   437  	case len(c.namespace) > 0 && len(subresources) > 0:
   438  		uncastRet, err = c.client.Fake.
   439  			Invokes(testing.NewPatchSubresourceAction(c.resource, c.namespace, name, pt, data, subresources...), &metav1.Status{Status: "dynamic patch fail"})
   440  
   441  	}
   442  
   443  	if err != nil {
   444  		return nil, err
   445  	}
   446  	if uncastRet == nil {
   447  		return nil, err
   448  	}
   449  
   450  	ret := &unstructured.Unstructured{}
   451  	if err := c.client.scheme.Convert(uncastRet, ret, nil); err != nil {
   452  		return nil, err
   453  	}
   454  	return ret, err
   455  }
   456  
   457  func convertObjectsToUnstructured(s *runtime.Scheme, objs []runtime.Object) ([]runtime.Object, error) {
   458  	ul := make([]runtime.Object, 0, len(objs))
   459  
   460  	for _, obj := range objs {
   461  		u, err := convertToUnstructured(s, obj)
   462  		if err != nil {
   463  			return nil, err
   464  		}
   465  
   466  		ul = append(ul, u)
   467  	}
   468  	return ul, nil
   469  }
   470  
   471  func convertToUnstructured(s *runtime.Scheme, obj runtime.Object) (runtime.Object, error) {
   472  	var (
   473  		err error
   474  		u   unstructured.Unstructured
   475  	)
   476  
   477  	u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
   478  	if err != nil {
   479  		return nil, fmt.Errorf("failed to convert to unstructured: %w", err)
   480  	}
   481  
   482  	gvk := u.GroupVersionKind()
   483  	if gvk.Group == "" || gvk.Kind == "" {
   484  		gvks, _, err := s.ObjectKinds(obj)
   485  		if err != nil {
   486  			return nil, fmt.Errorf("failed to convert to unstructured - unable to get GVK %w", err)
   487  		}
   488  		apiv, k := gvks[0].ToAPIVersionAndKind()
   489  		u.SetAPIVersion(apiv)
   490  		u.SetKind(k)
   491  	}
   492  	return &u, nil
   493  }