Extending kubernetes client-go

12/19/2020

I'm writing a controller that watches kubernetes service objects, and creates trafficsplits if they contain a certain label.

Since the native kubernetes go client does not support the trafficsplit object, I had to find a way and extend the client so it would recognize the custom resource. I found this guide which was helpful and allowed me to tackle the issue like so -

import (
    "splitClientV1alpha1 "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned/typed/split/v1alpha1"
    "k8s.io/client-go/kubernetes"
    ...
)

// getting ./kube/config from file
kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config")
// Building the config from file
kubeConfig, err = clientcmd.BuildConfigFromFlags("", kubehome)
if err != nil {
	return fmt.Errorf("error loading kubernetes configuration: %w", err)
}

// Creating the native client object
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
	return fmt.Errorf("error creating kubernetes client: %w", err)
}

// Creating another clientset exclusively for the custom resource
splitClient, err := splitClientV1alpha1.NewForConfig(kubeConfig)
if err != nil {
	return fmt.Errorf("error creating split client: %s", err)
}

I feel like there must be a way to extend the kubeClient object with the trafficsplit schema, instead of creating a separate client like I did. Is there any way to achieve this?

-- Yaron Idan
client-go
go
kubernetes
kubernetes-custom-resources

1 Answer

5/19/2021

This is definitely possible! You want to use go's struct extension features :)

Bsasically, we create a struct that extends both kubernetes.Clientset and splitClientV1alpha1.SplitV1alpha1Client and initialize it using code very similar to yours above. We can then use methods from either client on that struct.

import (
	"fmt"
	splitClientV1alpha1 "github.com/servicemeshinterface/smi-sdk-go/pkg/gen/client/split/clientset/versioned/typed/split/v1alpha1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

type MyKubeClient struct {
	kubernetes.Clientset
	splitClientV1alpha1.SplitV1alpha1Client
}

func getClient() (*MyKubeClient, error) {
	// getting ./kube/config from file
	kubehome := filepath.Join(homedir.HomeDir(), ".kube", "config")
	// Building the config from file
	kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubehome)
	if err != nil {
		return nil, fmt.Errorf("error loading kubernetes configuration: %w", err)
	}

	// Creating the native client object
	kubeClient, err := kubernetes.NewForConfig(kubeConfig)
	if err != nil {
		return nil, fmt.Errorf("error creating kubernetes client: %w", err)
	}

	// Creating another clientset exclusively for the custom resource
	splitClient, err := splitClientV1alpha1.NewForConfig(kubeConfig)
	if err != nil {
		return nil, fmt.Errorf("error creating split client: %s", err)
	}

	return &MyKubeClient{
		Clientset:           *kubeClient,
		SplitV1alpha1Client: *splitClient,
	}, nil
}

func doSomething() error {
	client, err := getClient()
	if err != nil {
		return err
	}

	client.CoreV1().Pods("").Create(...)
	client.TrafficSplits(...)
}

If you need to pass your custom client to a function that expects only the original kubernetes.ClientSet, you can do that with:

func doSomething() error {
	client, err := getClient()
	if err != nil {
		return err
	}

	useClientSet(&client.Clientset)
}

func useOriginalKubeClientSet(clientSet *kubernetes.Clientset) {
	# ... do things
}
-- saraf.gahl
Source: StackOverflow