...

Source file src/github.com/linkerd/linkerd2/pkg/k8s/fake.go

Documentation: github.com/linkerd/linkerd2/pkg/k8s

     1  package k8s
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  
    10  	spclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned"
    11  	spfake "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/fake"
    12  
    13  	spscheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme"
    14  	corev1 "k8s.io/api/core/v1"
    15  	discovery "k8s.io/api/discovery/v1"
    16  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    17  	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    18  	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    19  	apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/runtime"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/util/rand"
    24  	yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
    25  	discoveryfake "k8s.io/client-go/discovery/fake"
    26  	"k8s.io/client-go/kubernetes"
    27  	"k8s.io/client-go/kubernetes/fake"
    28  	"k8s.io/client-go/kubernetes/scheme"
    29  	"k8s.io/client-go/rest"
    30  	"k8s.io/client-go/testing"
    31  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    32  	apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
    33  	apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
    34  
    35  	"sigs.k8s.io/yaml"
    36  )
    37  
    38  func init() {
    39  	apiextensionsv1beta1.AddToScheme(scheme.Scheme)
    40  	apiextensionsv1.AddToScheme(scheme.Scheme)
    41  	apiregistrationv1.AddToScheme(scheme.Scheme)
    42  	spscheme.AddToScheme(scheme.Scheme)
    43  }
    44  
    45  // NewFakeAPI provides a mock KubernetesAPI backed by hard-coded resources
    46  func NewFakeAPI(configs ...string) (*KubernetesAPI, error) {
    47  	client, apiextClient, apiregClient, _, err := NewFakeClientSets(configs...)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	return &KubernetesAPI{
    53  		Config:          &rest.Config{},
    54  		Interface:       client,
    55  		Apiextensions:   apiextClient,
    56  		Apiregistration: apiregClient,
    57  	}, nil
    58  }
    59  
    60  // NewFakeAPIFromManifests reads from a slice of readers, each representing a
    61  // manifest or collection of manifests, and returns a mock KubernetesAPI.
    62  func NewFakeAPIFromManifests(readers []io.Reader) (*KubernetesAPI, error) {
    63  	client, apiextClient, apiregClient, _, err := newFakeClientSetsFromManifests(readers)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	return &KubernetesAPI{
    69  		Interface:       client,
    70  		Apiextensions:   apiextClient,
    71  		Apiregistration: apiregClient,
    72  	}, nil
    73  }
    74  
    75  // NewFakeClientSets provides mock Kubernetes ClientSets.
    76  // TODO: make this private once KubernetesAPI (and NewFakeAPI) supports spClient
    77  func NewFakeClientSets(configs ...string) (
    78  	*fake.Clientset,
    79  	apiextensionsclient.Interface,
    80  	apiregistrationclient.Interface,
    81  	spclient.Interface,
    82  	error,
    83  ) {
    84  	objs := []runtime.Object{}
    85  	apiextObjs := []runtime.Object{}
    86  	apiRegObjs := []runtime.Object{}
    87  	discoveryObjs := []runtime.Object{}
    88  	spObjs := []runtime.Object{}
    89  	for _, config := range configs {
    90  		obj, err := ToRuntimeObject(config)
    91  		if err != nil {
    92  			return nil, nil, nil, nil, err
    93  		}
    94  		switch strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) {
    95  		case "customresourcedefinition":
    96  			apiextObjs = append(apiextObjs, obj)
    97  		case "apiservice":
    98  			apiRegObjs = append(apiRegObjs, obj)
    99  		case "apiresourcelist":
   100  			discoveryObjs = append(discoveryObjs, obj)
   101  		case ServiceProfile:
   102  			spObjs = append(spObjs, obj)
   103  		case Server:
   104  			spObjs = append(spObjs, obj)
   105  		case ExtWorkload:
   106  			spObjs = append(spObjs, obj)
   107  		default:
   108  			objs = append(objs, obj)
   109  		}
   110  	}
   111  
   112  	endpointslice, err := ToRuntimeObject(`apiVersion: discovery.k8s.io/v1
   113  kind: EndpointSlice
   114  metadata:
   115    name: kubernetes
   116    namespace: default`)
   117  	if err != nil {
   118  		return nil, nil, nil, nil, err
   119  	}
   120  	objs = append(objs, endpointslice)
   121  
   122  	cs := fake.NewSimpleClientset(objs...)
   123  	fakeDiscoveryClient := cs.Discovery().(*discoveryfake.FakeDiscovery)
   124  	for _, obj := range discoveryObjs {
   125  		apiResList := obj.(*metav1.APIResourceList)
   126  		fakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, apiResList)
   127  	}
   128  	fakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, &metav1.APIResourceList{
   129  		TypeMeta: metav1.TypeMeta{
   130  			Kind:       "APIResourceList",
   131  			APIVersion: "v1",
   132  		},
   133  		GroupVersion: discovery.SchemeGroupVersion.String(),
   134  		APIResources: []metav1.APIResource{
   135  			{
   136  				Name:         "endpointslices",
   137  				Kind:         "EndpointSlice",
   138  				SingularName: "endpointslice",
   139  			},
   140  		},
   141  	})
   142  
   143  	// Add helpers to work with endpoint slice objects.
   144  	cs.PrependReactor("create", "endpointslices", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {
   145  		es := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)
   146  
   147  		// The API Server cannot generate a name when we use mocks, intercept
   148  		// the object and change its name
   149  		if es.GenerateName != "" {
   150  			es.Name = fmt.Sprintf("%s-%s", es.GenerateName, rand.String(8))
   151  			es.GenerateName = ""
   152  		}
   153  		es.Generation = 1
   154  
   155  		return false, es, nil
   156  	}))
   157  
   158  	cs.PrependReactor("update", "endpointslices", testing.ReactionFunc(func(action testing.Action) (bool, runtime.Object, error) {
   159  		// An update won't increase the generation since the API Server is
   160  		// mocked, so do a typecast and increment it here.
   161  		es := action.(testing.CreateAction).GetObject().(*discovery.EndpointSlice)
   162  		es.Generation++
   163  		return false, es, nil
   164  	}))
   165  
   166  	return cs,
   167  		apiextensionsfake.NewSimpleClientset(apiextObjs...),
   168  		apiregistrationfake.NewSimpleClientset(apiRegObjs...),
   169  		spfake.NewSimpleClientset(spObjs...),
   170  		nil
   171  }
   172  
   173  // newFakeClientSetsFromManifests reads from a slice of readers, each
   174  // representing a manifest or collection of manifests, and returns a mock
   175  // Kubernetes ClientSet.
   176  //
   177  //nolint:unparam
   178  func newFakeClientSetsFromManifests(readers []io.Reader) (
   179  	kubernetes.Interface,
   180  	apiextensionsclient.Interface,
   181  	apiregistrationclient.Interface,
   182  	spclient.Interface,
   183  	error,
   184  ) {
   185  	configs := []string{}
   186  
   187  	for _, reader := range readers {
   188  		r := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(reader, 4096))
   189  
   190  		// Iterate over all YAML objects in the input
   191  		for {
   192  			// Read a single YAML object
   193  			bytes, err := r.Read()
   194  			if err != nil {
   195  				if errors.Is(err, io.EOF) {
   196  					break
   197  				}
   198  				return nil, nil, nil, nil, err
   199  			}
   200  
   201  			// check for kind
   202  			var typeMeta metav1.TypeMeta
   203  			if err := yaml.Unmarshal(bytes, &typeMeta); err != nil {
   204  				return nil, nil, nil, nil, err
   205  			}
   206  
   207  			switch typeMeta.Kind {
   208  			case "":
   209  				// Kind missing from YAML, skipping
   210  
   211  			case "List":
   212  				var sourceList corev1.List
   213  				if err := yaml.Unmarshal(bytes, &sourceList); err != nil {
   214  					return nil, nil, nil, nil, err
   215  				}
   216  				for _, item := range sourceList.Items {
   217  					configs = append(configs, string(item.Raw))
   218  				}
   219  
   220  			default:
   221  				configs = append(configs, string(bytes))
   222  			}
   223  		}
   224  	}
   225  
   226  	return NewFakeClientSets(configs...)
   227  }
   228  
   229  // ToRuntimeObject deserializes Kubernetes YAML into a Runtime Object
   230  func ToRuntimeObject(config string) (runtime.Object, error) {
   231  	decode := scheme.Codecs.UniversalDeserializer().Decode
   232  	obj, _, err := decode([]byte(config), nil, nil)
   233  	return obj, err
   234  }
   235  
   236  // ObjectKinds wraps client-go's scheme.Scheme.ObjectKinds()
   237  // It returns all possible group,version,kind of the go object, true if the
   238  // object is considered unversioned, or an error if it's not a pointer or is
   239  // unregistered.
   240  func ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) {
   241  	apiextensionsv1beta1.AddToScheme(scheme.Scheme)
   242  	apiextensionsv1.AddToScheme(scheme.Scheme)
   243  	apiregistrationv1.AddToScheme(scheme.Scheme)
   244  	spscheme.AddToScheme(scheme.Scheme)
   245  	return scheme.Scheme.ObjectKinds(obj)
   246  }
   247  

View as plain text