package k8s import ( "bufio" "io" "strings" tsclient "github.com/deislabs/smi-sdk-go/pkg/gen/client/split/clientset/versioned" tsfake "github.com/deislabs/smi-sdk-go/pkg/gen/client/split/clientset/versioned/fake" spclient "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned" spfake "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/fake" tsscheme "github.com/deislabs/smi-sdk-go/pkg/gen/client/split/clientset/versioned/scheme" spscheme "github.com/linkerd/linkerd2/controller/gen/client/clientset/versioned/scheme" log "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" yamlDecoder "k8s.io/apimachinery/pkg/util/yaml" discoveryfake "k8s.io/client-go/discovery/fake" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" apiregistrationfake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake" "sigs.k8s.io/yaml" ) // NewFakeAPI provides a mock KubernetesAPI backed by hard-coded resources func NewFakeAPI(configs ...string) (*KubernetesAPI, error) { client, apiextClient, _, _, _, err := NewFakeClientSets(configs...) if err != nil { return nil, err } return &KubernetesAPI{ Config: &rest.Config{}, Interface: client, Apiextensions: apiextClient, }, nil } // NewFakeAPIFromManifests reads from a slice of readers, each representing a // manifest or collection of manifests, and returns a mock KubernetesAPI. func NewFakeAPIFromManifests(readers []io.Reader) (*KubernetesAPI, error) { client, apiextClient, _, _, _, err := newFakeClientSetsFromManifests(readers) if err != nil { return nil, err } return &KubernetesAPI{ Interface: client, Apiextensions: apiextClient, }, nil } // NewFakeClientSets provides mock Kubernetes ClientSets. // TODO: make this private once KubernetesAPI (and NewFakeAPI) supports spClient func NewFakeClientSets(configs ...string) ( kubernetes.Interface, apiextensionsclient.Interface, apiregistrationclient.Interface, spclient.Interface, tsclient.Interface, error, ) { objs := []runtime.Object{} apiextObjs := []runtime.Object{} apiRegObjs := []runtime.Object{} discoveryObjs := []runtime.Object{} spObjs := []runtime.Object{} tsObjs := []runtime.Object{} for _, config := range configs { obj, err := ToRuntimeObject(config) if err != nil { return nil, nil, nil, nil, nil, err } switch strings.ToLower(obj.GetObjectKind().GroupVersionKind().Kind) { case "customresourcedefinition": apiextObjs = append(apiextObjs, obj) case "apiservice": apiRegObjs = append(apiRegObjs, obj) case "apiresourcelist": discoveryObjs = append(discoveryObjs, obj) case ServiceProfile: spObjs = append(spObjs, obj) case TrafficSplit: tsObjs = append(tsObjs, obj) default: objs = append(objs, obj) } } cs := fake.NewSimpleClientset(objs...) fakeDiscoveryClient := cs.Discovery().(*discoveryfake.FakeDiscovery) for _, obj := range discoveryObjs { apiResList := obj.(*metav1.APIResourceList) fakeDiscoveryClient.Resources = append(fakeDiscoveryClient.Resources, apiResList) } return cs, apiextensionsfake.NewSimpleClientset(apiextObjs...), apiregistrationfake.NewSimpleClientset(apiRegObjs...), spfake.NewSimpleClientset(spObjs...), tsfake.NewSimpleClientset(tsObjs...), nil } // newFakeClientSetsFromManifests reads from a slice of readers, each // representing a manifest or collection of manifests, and returns a mock // Kubernetes ClientSet. func newFakeClientSetsFromManifests(readers []io.Reader) ( kubernetes.Interface, apiextensionsclient.Interface, apiregistrationclient.Interface, spclient.Interface, tsclient.Interface, error, ) { configs := []string{} for _, reader := range readers { r := yamlDecoder.NewYAMLReader(bufio.NewReaderSize(reader, 4096)) // Iterate over all YAML objects in the input for { // Read a single YAML object bytes, err := r.Read() if err == io.EOF { break } if err != nil { return nil, nil, nil, nil, nil, err } // check for kind var typeMeta metav1.TypeMeta if err := yaml.Unmarshal(bytes, &typeMeta); err != nil { return nil, nil, nil, nil, nil, err } switch typeMeta.Kind { case "": log.Warnf("Kind missing from YAML, skipping") case "List": var sourceList corev1.List if err := yaml.Unmarshal(bytes, &sourceList); err != nil { return nil, nil, nil, nil, nil, err } for _, item := range sourceList.Items { configs = append(configs, string(item.Raw)) } default: configs = append(configs, string(bytes)) } } } return NewFakeClientSets(configs...) } // ToRuntimeObject deserializes Kubernetes YAML into a Runtime Object func ToRuntimeObject(config string) (runtime.Object, error) { apiextensionsv1beta1.AddToScheme(scheme.Scheme) apiregistrationv1.AddToScheme(scheme.Scheme) spscheme.AddToScheme(scheme.Scheme) tsscheme.AddToScheme(scheme.Scheme) decode := scheme.Codecs.UniversalDeserializer().Decode obj, _, err := decode([]byte(config), nil, nil) return obj, err } // ObjectKinds wraps client-go's scheme.Scheme.ObjectKinds() // It returns all possible group,version,kind of the go object, true if the // object is considered unversioned, or an error if it's not a pointer or is // unregistered. func ObjectKinds(obj runtime.Object) ([]schema.GroupVersionKind, bool, error) { apiextensionsv1beta1.AddToScheme(scheme.Scheme) apiregistrationv1.AddToScheme(scheme.Scheme) spscheme.AddToScheme(scheme.Scheme) tsscheme.AddToScheme(scheme.Scheme) return scheme.Scheme.ObjectKinds(obj) }