Repository: kubernetes-sigs/kwok
Pull request #690 (2023-06-29 07:24:07)

Clean up clientset
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide/first-contribution.md#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/sig-testing/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

#### What type of PR is this?

<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->

/kind cleanup

#### What this PR does / why we need it:

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->
```release-note
NONE
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:

<!--
This section can be blank if this pull request does not require a release note.

When adding links which point to resources within git repositories, like
KEPs or supporting documentation, please reference a specific commit and avoid
linking directly to the master branch. This ensures that links reference a
specific point in time, rather than a document that may change over time.

See here for guidance on getting permanent links to files: https://help.github.com/en/articles/getting-permanent-links-to-files

Please use the following format for linking documentation:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs
NONE
```


netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *k8s-kwok* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | ccc333fdff2af70c0e8c4c910323e261df3c4feb |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/k8s-kwok/deploys/649d31988d3cba0008c34439 |
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/kwok/pull/690#" title="Author self-approved">wzshiming</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fkwok).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/kwok/blob/main/OWNERS)~~ [wzshiming]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/pkg/kwok/cmd/root.go b/pkg/kwok/cmd/root.go
index f5f7ef22..d531c173 100644
--- a/pkg/kwok/cmd/root.go
+++ b/pkg/kwok/cmd/root.go
@@ -121,6 +121,10 @@ func runE(ctx context.Context, flags *flagpole) error {
 	if err != nil {
 		return err
 	}
+	typedKwokClient, err := clientset.ToTypedKwokClient()
+	if err != nil {
+		return err
+	}
 
 	if flags.Options.ManageAllNodes {
 		if flags.Options.ManageNodesWithAnnotationSelector != "" || flags.Options.ManageNodesWithLabelSelector != "" {
@@ -174,7 +178,7 @@ func runE(ctx context.Context, flags *flagpole) error {
 
 	ctr, err := controllers.NewController(controllers.Config{
 		Clock:                                 clock.RealClock{},
-		ClientSet:                             typedClient,
+		TypedClient:                           typedClient,
 		EnableCNI:                             flags.Options.EnableCNI,
 		ManageAllNodes:                        flags.Options.ManageAllNodes,
 		ManageNodesWithAnnotationSelector:     flags.Options.ManageNodesWithAnnotationSelector,
@@ -212,7 +216,7 @@ func runE(ctx context.Context, flags *flagpole) error {
 		clusterAttaches := config.FilterWithTypeFromContext[*internalversion.ClusterAttach](ctx)
 		attaches := config.FilterWithTypeFromContext[*internalversion.Attach](ctx)
 		config := server.Config{
-			Client:              clientset,
+			TypedKwokClient:     typedKwokClient,
 			EnableCRDs:          flags.Options.EnableCRDs,
 			ClusterPortForwards: clusterPortForwards,
 			PortForwards:        portForwards,
diff --git a/pkg/kwok/controllers/controller.go b/pkg/kwok/controllers/controller.go
index b2676927..41ecae93 100644
--- a/pkg/kwok/controllers/controller.go
+++ b/pkg/kwok/controllers/controller.go
@@ -89,14 +89,14 @@ type Controller struct {
 	pods        *PodController
 	nodeLeases  *NodeLeaseController
 	broadcaster record.EventBroadcaster
-	clientSet   kubernetes.Interface
+	typedClient kubernetes.Interface
 }
 
 // Config is the configuration for the controller
 type Config struct {
 	Clock                                 clock.Clock
 	EnableCNI                             bool
-	ClientSet                             kubernetes.Interface
+	TypedClient                           kubernetes.Interface
 	ManageAllNodes                        bool
 	ManageNodesWithAnnotationSelector     string
 	ManageNodesWithLabelSelector          string
@@ -162,7 +162,7 @@ func NewController(conf Config) (*Controller, error) {
 		renewIntervalJitter := 0.04
 		l, err := NewNodeLeaseController(NodeLeaseControllerConfig{
 			Clock:                conf.Clock,
-			ClientSet:            conf.ClientSet,
+			TypedClient:          conf.TypedClient,
 			LeaseDurationSeconds: conf.NodeLeaseDurationSeconds,
 			LeaseParallelism:     conf.NodeLeaseParallelism,
 			RenewInterval:        renewInterval,
@@ -189,7 +189,7 @@ func NewController(conf Config) (*Controller, error) {
 
 	nodes, err := NewNodeController(NodeControllerConfig{
 		Clock:                                 conf.Clock,
-		ClientSet:                             conf.ClientSet,
+		TypedClient:                           conf.TypedClient,
 		NodeIP:                                conf.NodeIP,
 		NodeName:                              conf.NodeName,
 		NodePort:                              conf.NodePort,
@@ -217,7 +217,7 @@ func NewController(conf Config) (*Controller, error) {
 	pods, err := NewPodController(PodControllerConfig{
 		Clock:                                 conf.Clock,
 		EnableCNI:                             conf.EnableCNI,
-		ClientSet:                             conf.ClientSet,
+		TypedClient:                           conf.TypedClient,
 		NodeIP:                                conf.NodeIP,
 		CIDR:                                  conf.CIDR,
 		DisregardStatusWithAnnotationSelector: conf.DisregardStatusWithAnnotationSelector,
@@ -265,7 +265,7 @@ func NewController(conf Config) (*Controller, error) {
 		nodes:       nodes,
 		nodeLeases:  nodeLeases,
 		broadcaster: eventBroadcaster,
-		clientSet:   conf.ClientSet,
+		typedClient: conf.TypedClient,
 	}
 
 	return n, nil
@@ -273,7 +273,7 @@ func NewController(conf Config) (*Controller, error) {
 
 // Start starts the controller
 func (c *Controller) Start(ctx context.Context) error {
-	c.broadcaster.StartRecordingToSink(&clientcorev1.EventSinkImpl{Interface: c.clientSet.CoreV1().Events("")})
+	c.broadcaster.StartRecordingToSink(&clientcorev1.EventSinkImpl{Interface: c.typedClient.CoreV1().Events("")})
 	if c.nodeLeases != nil {
 		err := c.nodeLeases.Start(ctx)
 		if err != nil {
diff --git a/pkg/kwok/controllers/node_controller.go b/pkg/kwok/controllers/node_controller.go
index 303b4bd3..c60da033 100644
--- a/pkg/kwok/controllers/node_controller.go
+++ b/pkg/kwok/controllers/node_controller.go
@@ -90,7 +90,7 @@ var (
 // NodeController is a fake nodes implementation that can be used to test
 type NodeController struct {
 	clock                                 clock.Clock
-	clientSet                             kubernetes.Interface
+	typedClient                           kubernetes.Interface
 	nodeIP                                string
 	nodeName                              string
 	nodePort                              int
@@ -115,7 +115,7 @@ type NodeController struct {
 // NodeControllerConfig is the configuration for the NodeController
 type NodeControllerConfig struct {
 	Clock                                 clock.Clock
-	ClientSet                             kubernetes.Interface
+	TypedClient                           kubernetes.Interface
 	NodeSelectorFunc                      func(node *corev1.Node) bool
 	OnNodeManagedFunc                     func(nodeName string)
 	DisregardStatusWithAnnotationSelector string
@@ -167,7 +167,7 @@ func NewNodeController(conf NodeControllerConfig) (*NodeController, error) {
 
 	c := &NodeController{
 		clock:                                 conf.Clock,
-		clientSet:                             conf.ClientSet,
+		typedClient:                           conf.TypedClient,
 		nodeSelectorFunc:                      conf.NodeSelectorFunc,
 		disregardStatusWithAnnotationSelector: disregardStatusWithAnnotationSelector,
 		disregardStatusWithLabelSelector:      disregardStatusWithLabelSelector,
@@ -248,7 +248,7 @@ func (c *NodeController) need(node *corev1.Node) bool {
 // watchResources watch resources and send to preprocessChan
 func (c *NodeController) watchResources(ctx context.Context, opt metav1.ListOptions) error {
 	// Watch nodes in the cluster
-	watcher, err := c.clientSet.CoreV1().Nodes().Watch(ctx, opt)
+	watcher, err := c.typedClient.CoreV1().Nodes().Watch(ctx, opt)
 	if err != nil {
 		return err
 	}
@@ -262,7 +262,7 @@ func (c *NodeController) watchResources(ctx context.Context, opt metav1.ListOpti
 			case event, ok := <-rc:
 				if !ok {
 					for {
-						watcher, err := c.clientSet.CoreV1().Nodes().Watch(ctx, opt)
+						watcher, err := c.typedClient.CoreV1().Nodes().Watch(ctx, opt)
 						if err == nil {
 							rc = watcher.ResultChan()
 							continue loop
@@ -334,7 +334,7 @@ func (c *NodeController) watchResources(ctx context.Context, opt metav1.ListOpti
 // listResources lists all resources and sends to preprocessChan
 func (c *NodeController) listResources(ctx context.Context, opt metav1.ListOptions) error {
 	listPager := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
-		return c.clientSet.CoreV1().Nodes().List(ctx, opts)
+		return c.typedClient.CoreV1().Nodes().List(ctx, opts)
 	})
 
 	logger := log.FromContext(ctx)
@@ -372,7 +372,7 @@ func (c *NodeController) finalizersModify(ctx context.Context, node *corev1.Node
 		"node", node.Name,
 	)
 
-	result, err := c.clientSet.CoreV1().Nodes().Patch(ctx, node.Name, types.JSONPatchType, data, metav1.PatchOptions{})
+	result, err := c.typedClient.CoreV1().Nodes().Patch(ctx, node.Name, types.JSONPatchType, data, metav1.PatchOptions{})
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Patch node finalizers",
@@ -393,7 +393,7 @@ func (c *NodeController) deleteResource(ctx context.Context, node *corev1.Node)
 		"node", node.Name,
 	)
 
-	err := c.clientSet.CoreV1().Nodes().Delete(ctx, node.Name, deleteOpt)
+	err := c.typedClient.CoreV1().Nodes().Delete(ctx, node.Name, deleteOpt)
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Delete node",
@@ -599,7 +599,7 @@ func (c *NodeController) patchResource(ctx context.Context, node *corev1.Node, p
 		"node", node.Name,
 	)
 
-	result, err := c.clientSet.CoreV1().Nodes().Patch(ctx, node.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status")
+	result, err := c.typedClient.CoreV1().Nodes().Patch(ctx, node.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status")
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Patch node",
diff --git a/pkg/kwok/controllers/node_lease_controller.go b/pkg/kwok/controllers/node_lease_controller.go
index 84094edb..e875c974 100644
--- a/pkg/kwok/controllers/node_lease_controller.go
+++ b/pkg/kwok/controllers/node_lease_controller.go
@@ -38,7 +38,7 @@ import (
 
 // NodeLeaseController is responsible for creating and renewing a lease object
 type NodeLeaseController struct {
-	clientSet            clientset.Interface
+	typedClient          clientset.Interface
 	leaseNamespace       string
 	leaseDurationSeconds uint
 	leaseParallelism     uint
@@ -65,7 +65,7 @@ type NodeLeaseController struct {
 type NodeLeaseControllerConfig struct {
 	Clock                clock.Clock
 	HolderIdentity       string
-	ClientSet            clientset.Interface
+	TypedClient          clientset.Interface
 	LeaseDurationSeconds uint
 	LeaseParallelism     uint
 	RenewInterval        time.Duration
@@ -87,7 +87,7 @@ func NewNodeLeaseController(conf NodeLeaseControllerConfig) (*NodeLeaseControlle
 
 	c := &NodeLeaseController{
 		clock:                conf.Clock,
-		clientSet:            conf.ClientSet,
+		typedClient:          conf.TypedClient,
 		leaseNamespace:       conf.LeaseNamespace,
 		leaseDurationSeconds: conf.LeaseDurationSeconds,
 		leaseParallelism:     conf.LeaseParallelism,
@@ -128,7 +128,7 @@ func (c *NodeLeaseController) Start(ctx context.Context) error {
 // watchResources watch resources and send to preprocessChan
 func (c *NodeLeaseController) watchResources(ctx context.Context, opt metav1.ListOptions) error {
 	// Watch node leases in the cluster
-	watcher, err := c.clientSet.CoordinationV1().Leases(c.leaseNamespace).Watch(ctx, opt)
+	watcher, err := c.typedClient.CoordinationV1().Leases(c.leaseNamespace).Watch(ctx, opt)
 	if err != nil {
 		return err
 	}
@@ -142,7 +142,7 @@ func (c *NodeLeaseController) watchResources(ctx context.Context, opt metav1.Lis
 			case event, ok := <-rc:
 				if !ok {
 					for {
-						watcher, err := c.clientSet.CoordinationV1().Leases(c.leaseNamespace).Watch(ctx, opt)
+						watcher, err := c.typedClient.CoordinationV1().Leases(c.leaseNamespace).Watch(ctx, opt)
 						if err == nil {
 							rc = watcher.ResultChan()
 							continue loop
@@ -177,7 +177,7 @@ func (c *NodeLeaseController) watchResources(ctx context.Context, opt metav1.Lis
 // listResources lists all resources and sends to preprocessChan
 func (c *NodeLeaseController) listResources(ctx context.Context, opt metav1.ListOptions) error {
 	listPager := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
-		return c.clientSet.CoordinationV1().Leases(c.leaseNamespace).List(ctx, opts)
+		return c.typedClient.CoordinationV1().Leases(c.leaseNamespace).List(ctx, opts)
 	})
 
 	return listPager.EachListItem(ctx, opt, func(obj runtime.Object) error {
@@ -322,7 +322,7 @@ func (c *NodeLeaseController) ensureLease(ctx context.Context, leaseName string)
 		}
 	}
 
-	lease, err := c.clientSet.CoordinationV1().Leases(c.leaseNamespace).Create(ctx, lease, metav1.CreateOptions{})
+	lease, err := c.typedClient.CoordinationV1().Leases(c.leaseNamespace).Create(ctx, lease, metav1.CreateOptions{})
 	if err != nil {
 		return nil, err
 	}
@@ -348,7 +348,7 @@ func (c *NodeLeaseController) renewLease(ctx context.Context, base *coordination
 		}
 	}
 
-	lease, err := c.clientSet.CoordinationV1().Leases(c.leaseNamespace).Update(ctx, lease, metav1.UpdateOptions{})
+	lease, err := c.typedClient.CoordinationV1().Leases(c.leaseNamespace).Update(ctx, lease, metav1.UpdateOptions{})
 	if err != nil {
 		return nil, false, err
 	}
diff --git a/pkg/kwok/controllers/pod_controller.go b/pkg/kwok/controllers/pod_controller.go
index 3808efe4..9b97da13 100644
--- a/pkg/kwok/controllers/pod_controller.go
+++ b/pkg/kwok/controllers/pod_controller.go
@@ -55,7 +55,7 @@ var (
 type PodController struct {
 	clock                                 clock.Clock
 	enableCNI                             bool
-	clientSet                             kubernetes.Interface
+	typedClient                           kubernetes.Interface
 	disregardStatusWithAnnotationSelector labels.Selector
 	disregardStatusWithLabelSelector      labels.Selector
 	nodeIP                                string
@@ -80,7 +80,7 @@ type PodController struct {
 type PodControllerConfig struct {
 	Clock                                 clock.Clock
 	EnableCNI                             bool
-	ClientSet                             kubernetes.Interface
+	TypedClient                           kubernetes.Interface
 	DisregardStatusWithAnnotationSelector string
 	DisregardStatusWithLabelSelector      string
 	NodeIP                                string
@@ -123,7 +123,7 @@ func NewPodController(conf PodControllerConfig) (*PodController, error) {
 	c := &PodController{
 		clock:                                 conf.Clock,
 		enableCNI:                             conf.EnableCNI,
-		clientSet:                             conf.ClientSet,
+		typedClient:                           conf.TypedClient,
 		disregardStatusWithAnnotationSelector: disregardStatusWithAnnotationSelector,
 		disregardStatusWithLabelSelector:      disregardStatusWithLabelSelector,
 		nodeIP:                                conf.NodeIP,
@@ -196,7 +196,7 @@ func (c *PodController) finalizersModify(ctx context.Context, pod *corev1.Pod, f
 		"node", pod.Spec.NodeName,
 	)
 
-	result, err := c.clientSet.CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, types.JSONPatchType, data, metav1.PatchOptions{})
+	result, err := c.typedClient.CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, types.JSONPatchType, data, metav1.PatchOptions{})
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Patch pod finalizers",
@@ -218,7 +218,7 @@ func (c *PodController) deleteResource(ctx context.Context, pod *corev1.Pod) err
 		"node", pod.Spec.NodeName,
 	)
 
-	err := c.clientSet.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, deleteOpt)
+	err := c.typedClient.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, deleteOpt)
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Delete pod",
@@ -422,7 +422,7 @@ func (c *PodController) patchResource(ctx context.Context, pod *corev1.Pod, patc
 		"node", pod.Spec.NodeName,
 	)
 
-	result, err := c.clientSet.CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status")
+	result, err := c.typedClient.CoreV1().Pods(pod.Namespace).Patch(ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "status")
 	if err != nil {
 		if apierrors.IsNotFound(err) {
 			logger.Warn("Patch pod",
@@ -457,7 +457,7 @@ func (c *PodController) need(pod *corev1.Pod) bool {
 
 // watchResources watch resources and send to preprocessChan
 func (c *PodController) watchResources(ctx context.Context, opt metav1.ListOptions) error {
-	watcher, err := c.clientSet.CoreV1().Pods(c.namespace).Watch(ctx, opt)
+	watcher, err := c.typedClient.CoreV1().Pods(c.namespace).Watch(ctx, opt)
 	if err != nil {
 		return err
 	}
@@ -471,7 +471,7 @@ func (c *PodController) watchResources(ctx context.Context, opt metav1.ListOptio
 			case event, ok := <-rc:
 				if !ok {
 					for {
-						watcher, err := c.clientSet.CoreV1().Pods(c.namespace).Watch(ctx, opt)
+						watcher, err := c.typedClient.CoreV1().Pods(c.namespace).Watch(ctx, opt)
 						if err == nil {
 							rc = watcher.ResultChan()
 							continue loop
@@ -546,7 +546,7 @@ func (c *PodController) watchResources(ctx context.Context, opt metav1.ListOptio
 // listResources lists all resources and sends to preprocessChan
 func (c *PodController) listResources(ctx context.Context, opt metav1.ListOptions) error {
 	listPager := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
-		return c.clientSet.CoreV1().Pods(c.namespace).List(ctx, opts)
+		return c.typedClient.CoreV1().Pods(c.namespace).List(ctx, opts)
 	})
 
 	logger := log.FromContext(ctx)
diff --git a/pkg/kwok/server/server.go b/pkg/kwok/server/server.go
index 69c6f7e8..953d27b8 100644
--- a/pkg/kwok/server/server.go
+++ b/pkg/kwok/server/server.go
@@ -30,10 +30,10 @@ import (
 
 	"sigs.k8s.io/kwok/pkg/apis/internalversion"
 	"sigs.k8s.io/kwok/pkg/apis/v1alpha1"
+	"sigs.k8s.io/kwok/pkg/client/clientset/versioned"
 	"sigs.k8s.io/kwok/pkg/config/resources"
 	"sigs.k8s.io/kwok/pkg/kwok/controllers"
 	"sigs.k8s.io/kwok/pkg/log"
-	"sigs.k8s.io/kwok/pkg/utils/client"
 	"sigs.k8s.io/kwok/pkg/utils/pools"
 )
 
@@ -43,7 +43,8 @@ const (
 
 // Server is a server that can serve HTTP/HTTPS requests.
 type Server struct {
-	client     *client.Clientset
+	typedKwokClient versioned.Interface
+
 	enableCRDs []string
 
 	restfulCont *restful.Container
@@ -67,8 +68,8 @@ type Server struct {
 
 // Config holds configurations needed by the server handlers.
 type Config struct {
-	Client     *client.Clientset
-	EnableCRDs []string
+	TypedKwokClient versioned.Interface
+	EnableCRDs      []string
 
 	ClusterPortForwards []*internalversion.ClusterPortForward
 	PortForwards        []*internalversion.PortForward
@@ -87,7 +88,7 @@ func NewServer(config Config) (*Server, error) {
 	container := restful.NewContainer()
 
 	s := &Server{
-		client:                config.Client,
+		typedKwokClient:       config.TypedKwokClient,
 		enableCRDs:            config.EnableCRDs,
 		restfulCont:           container,
 		idleTimeout:           1 * time.Hour,
@@ -114,10 +115,7 @@ func NewServer(config Config) (*Server, error) {
 }
 
 func (s *Server) initWatchCRD(ctx context.Context) ([]resources.Starter, error) {
-	cli, err := s.client.ToTypedKwokClient()
-	if err != nil {
-		return nil, err
-	}
+	cli := s.typedKwokClient
 
 	starters := []resources.Starter{}
 
diff --git a/pkg/kwokctl/snapshot/load.go b/pkg/kwokctl/snapshot/load.go
index d7b351a3..65b15f0b 100644
--- a/pkg/kwokctl/snapshot/load.go
+++ b/pkg/kwokctl/snapshot/load.go
@@ -64,8 +64,8 @@ type loader struct {
 	exist   map[uniqueKey]types.UID
 	pending map[uniqueKey][]*unstructured.Unstructured
 
-	restMapper meta.RESTMapper
-	dynClient  *dynamic.DynamicClient
+	restMapper    meta.RESTMapper
+	dynamicClient dynamic.Interface
 }
 
 func newLoader(kubeconfigPath string) (*loader, error) {
@@ -84,11 +84,11 @@ func newLoader(kubeconfigPath string) (*loader, error) {
 	}
 
 	return &loader{
-		filterMap:  make(map[schema.GroupKind]struct{}),
-		exist:      make(map[uniqueKey]types.UID),
-		pending:    make(map[uniqueKey][]*unstructured.Unstructured),
-		restMapper: restMapper,
-		dynClient:  dynClient,
+		filterMap:     make(map[schema.GroupKind]struct{}),
+		exist:         make(map[uniqueKey]types.UID),
+		pending:       make(map[uniqueKey][]*unstructured.Unstructured),
+		restMapper:    restMapper,
+		dynamicClient: dynClient,
 	}, nil
 }
 
@@ -246,7 +246,7 @@ func (l *loader) apply(ctx context.Context, obj *unstructured.Unstructured) *uns
 
 	clearUnstructured(obj)
 
-	nri := l.dynClient.Resource(gvr)
+	nri := l.dynamicClient.Resource(gvr)
 	var ri dynamic.ResourceInterface = nri
 
 	if ns := obj.GetNamespace(); ns != "" {
diff --git a/pkg/kwokctl/snapshot/save.go b/pkg/kwokctl/snapshot/save.go
index b2647818..d8f8df61 100644
--- a/pkg/kwokctl/snapshot/save.go
+++ b/pkg/kwokctl/snapshot/save.go
@@ -63,7 +63,7 @@ func Save(ctx context.Context, kubeconfigPath string, w io.Writer, resources []s
 	if err != nil {
 		return fmt.Errorf("failed to get rest mapper: %w", err)
 	}
-	dynClient, err := clientset.ToDynamicClient()
+	dynamicClient, err := clientset.ToDynamicClient()
 	if err != nil {
 		return fmt.Errorf("failed to create dynamic client: %w", err)
 	}
@@ -84,7 +84,7 @@ func Save(ctx context.Context, kubeconfigPath string, w io.Writer, resources []s
 	totalCount := 0
 	start := time.Now()
 	for _, gvr := range gvrs {
-		nri := dynClient.Resource(gvr)
+		nri := dynamicClient.Resource(gvr)
 		logger := logger.With("resource", gvr.Resource)
 
 		start := time.Now()
diff --git a/pkg/utils/client/client.go b/pkg/utils/client/clientset.go
similarity index 79%
rename from pkg/utils/client/client.go
rename to pkg/utils/client/clientset.go
index f89fb9dd..011decfd 100644
--- a/pkg/utils/client/client.go
+++ b/pkg/utils/client/clientset.go
@@ -34,7 +34,19 @@ import (
 )
 
 // Clientset is a set of Kubernetes clients.
-type Clientset struct {
+type Clientset interface {
+	ToRESTConfig() (*rest.Config, error)
+	ToRawKubeConfigLoader() clientcmd.ClientConfig
+	ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
+	ToRESTMapper() (meta.RESTMapper, error)
+	ToRESTClient() (*rest.RESTClient, error)
+	ToTypedClient() (kubernetes.Interface, error)
+	ToTypedKwokClient() (versioned.Interface, error)
+	ToDynamicClient() (dynamic.Interface, error)
+}
+
+// clientset is a set of Kubernetes clients.
+type clientset struct {
 	masterURL       string
 	kubeconfigPath  string
 	restConfig      *rest.Config
@@ -49,19 +61,19 @@ type Clientset struct {
 	opts []Option
 }
 
-// Option is a function that configures a Clientset.
-type Option func(*Clientset)
+// Option is a function that configures a clientset.
+type Option func(*clientset)
 
 // WithImpersonate sets the impersonation config.
 func WithImpersonate(impersonateConfig rest.ImpersonationConfig) Option {
-	return func(c *Clientset) {
+	return func(c *clientset) {
 		c.restConfig.Impersonate = impersonateConfig
 	}
 }
 
-// NewClientset creates a new Clientset.
-func NewClientset(masterURL, kubeconfigPath string, opts ...Option) (*Clientset, error) {
-	return &Clientset{
+// NewClientset creates a new clientset.
+func NewClientset(masterURL, kubeconfigPath string, opts ...Option) (Clientset, error) {
+	return &clientset{
 		masterURL:      masterURL,
 		kubeconfigPath: kubeconfigPath,
 		opts:           opts,
@@ -69,7 +81,7 @@ func NewClientset(masterURL, kubeconfigPath string, opts ...Option) (*Clientset,
 }
 
 // ToRESTConfig returns a REST config.
-func (g *Clientset) ToRESTConfig() (*rest.Config, error) {
+func (g *clientset) ToRESTConfig() (*rest.Config, error) {
 	if g.restConfig == nil {
 		var restConfig *rest.Config
 		if g.kubeconfigPath == "" && g.masterURL == "" {
@@ -98,20 +110,20 @@ func (g *Clientset) ToRESTConfig() (*rest.Config, error) {
 }
 
 // ToDiscoveryClient returns a discovery client.
-func (g *Clientset) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
+func (g *clientset) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
 	if g.discoveryClient == nil {
-		clientset, err := g.ToTypedClient()
+		typedClient, err := g.ToTypedClient()
 		if err != nil {
 			return nil, err
 		}
-		discoveryClient := &cachedDiscoveryInterface{clientset.DiscoveryClient}
+		discoveryClient := &cachedDiscoveryInterface{typedClient.Discovery()}
 		g.discoveryClient = discoveryClient
 	}
 	return g.discoveryClient, nil
 }
 
 // ToRESTMapper returns a REST mapper.
-func (g *Clientset) ToRESTMapper() (meta.RESTMapper, error) {
+func (g *clientset) ToRESTMapper() (meta.RESTMapper, error) {
 	if g.restMapper == nil {
 		discoveryClient, err := g.ToDiscoveryClient()
 		if err != nil {
@@ -126,7 +138,7 @@ func (g *Clientset) ToRESTMapper() (meta.RESTMapper, error) {
 }
 
 // ToRESTClient returns a REST client.
-func (g *Clientset) ToRESTClient() (*rest.RESTClient, error) {
+func (g *clientset) ToRESTClient() (*rest.RESTClient, error) {
 	if g.restClient == nil {
 		restConfig, err := g.ToRESTConfig()
 		if err != nil {
@@ -142,7 +154,7 @@ func (g *Clientset) ToRESTClient() (*rest.RESTClient, error) {
 }
 
 // ToRawKubeConfigLoader returns a raw kubeconfig loader.
-func (g *Clientset) ToRawKubeConfigLoader() clientcmd.ClientConfig {
+func (g *clientset) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 	if g.clientConfig == nil {
 		g.clientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 			&clientcmd.ClientConfigLoadingRules{ExplicitPath: g.kubeconfigPath},
@@ -152,7 +164,7 @@ func (g *Clientset) ToRawKubeConfigLoader() clientcmd.ClientConfig {
 }
 
 // ToTypedKwokClient returns a typed kwok client.
-func (g *Clientset) ToTypedKwokClient() (*versioned.Clientset, error) {
+func (g *clientset) ToTypedKwokClient() (versioned.Interface, error) {
 	if g.kwokClient == nil {
 		restConfig, err := g.ToRESTConfig()
 		if err != nil {
@@ -160,7 +172,7 @@ func (g *Clientset) ToTypedKwokClient() (*versioned.Clientset, error) {
 		}
 		typedKwokClient, err := versioned.NewForConfig(restConfig)
 		if err != nil {
-			return nil, fmt.Errorf("could not get Kubernetes typedClient: %w", err)
+			return nil, fmt.Errorf("could not get kwok typedClient: %w", err)
 		}
 		g.kwokClient = typedKwokClient
 	}
@@ -168,7 +180,7 @@ func (g *Clientset) ToTypedKwokClient() (*versioned.Clientset, error) {
 }
 
 // ToTypedClient returns a typed Kubernetes client.
-func (g *Clientset) ToTypedClient() (*kubernetes.Clientset, error) {
+func (g *clientset) ToTypedClient() (kubernetes.Interface, error) {
 	if g.typedClient == nil {
 		restConfig, err := g.ToRESTConfig()
 		if err != nil {
@@ -184,7 +196,7 @@ func (g *Clientset) ToTypedClient() (*kubernetes.Clientset, error) {
 }
 
 // ToDynamicClient returns a dynamic Kubernetes client.
-func (g *Clientset) ToDynamicClient() (*dynamic.DynamicClient, error) {
+func (g *clientset) ToDynamicClient() (dynamic.Interface, error) {
 	if g.dynamicClient == nil {
 		restConfig, err := g.ToRESTConfig()
 		if err != nil {


Test patch:
diff --git a/pkg/kwok/controllers/controller_test.go b/pkg/kwok/controllers/controller_test.go
index 89f3bfe6..56774593 100644
--- a/pkg/kwok/controllers/controller_test.go
+++ b/pkg/kwok/controllers/controller_test.go
@@ -81,7 +81,7 @@ func TestController(t *testing.T) {
 		{
 			name: "node controller test: manage all nodes",
 			conf: Config{
-				ClientSet:                fake.NewSimpleClientset(nodes...),
+				TypedClient:              fake.NewSimpleClientset(nodes...),
 				ManageAllNodes:           true,
 				NodeStages:               nodeStages,
 				PodStages:                podStages,
@@ -99,7 +99,7 @@ func TestController(t *testing.T) {
 		{
 			name: "node controller test: manage nodes with label selector `manage-by-kwok=true`",
 			conf: Config{
-				ClientSet:                    fake.NewSimpleClientset(nodes...),
+				TypedClient:                  fake.NewSimpleClientset(nodes...),
 				ManageNodesWithLabelSelector: "manage-by-kwok",
 				NodeStages:                   nodeStages,
 				PodStages:                    podStages,
@@ -117,7 +117,7 @@ func TestController(t *testing.T) {
 		{
 			name: "node controller test: manage nodes with annotation selector `manage-by-kwok=true`",
 			conf: Config{
-				ClientSet:                         fake.NewSimpleClientset(nodes...),
+				TypedClient:                       fake.NewSimpleClientset(nodes...),
 				ManageNodesWithAnnotationSelector: "manage-by-kwok=true",
 				NodeStages:                        nodeStages,
 				PodStages:                         podStages,
@@ -155,7 +155,7 @@ func TestController(t *testing.T) {
 
 			// wait for nodes to be right phase indicated by `tt.wantNodePhase`
 			err = wait.Poll(ctx, func(ctx context.Context) (done bool, err error) {
-				list, err := ctr.clientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
+				list, err := ctr.typedClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
 				if err != nil {
 					return false, fmt.Errorf("failed to list nodes, err: %w", err)
 				}
diff --git a/pkg/kwok/controllers/node_controller_test.go b/pkg/kwok/controllers/node_controller_test.go
index 5037bf73..2619cfd5 100644
--- a/pkg/kwok/controllers/node_controller_test.go
+++ b/pkg/kwok/controllers/node_controller_test.go
@@ -72,7 +72,7 @@ func TestNodeController(t *testing.T) {
 	nodeHeartbeatStages, _ := NewStagesFromYaml([]byte(stages.DefaultNodeHeartbeatStages))
 	nodeStages = append(nodeStages, nodeHeartbeatStages...)
 	nodes, err := NewNodeController(NodeControllerConfig{
-		ClientSet:            clientset,
+		TypedClient:          clientset,
 		NodeIP:               "10.0.0.1",
 		NodeSelectorFunc:     nodeSelectorFunc,
 		Stages:               nodeStages,
diff --git a/pkg/kwok/controllers/node_lease_controller_test.go b/pkg/kwok/controllers/node_lease_controller_test.go
index 08a77437..47b2f9bf 100644
--- a/pkg/kwok/controllers/node_lease_controller_test.go
+++ b/pkg/kwok/controllers/node_lease_controller_test.go
@@ -71,7 +71,7 @@ func TestNodeLeaseController(t *testing.T) {
 	)
 
 	nodeLeases, err := NewNodeLeaseController(NodeLeaseControllerConfig{
-		ClientSet:            clientset,
+		TypedClient:          clientset,
 		HolderIdentity:       "test",
 		LeaseDurationSeconds: 40,
 		LeaseParallelism:     2,
diff --git a/pkg/kwok/controllers/pod_controller_test.go b/pkg/kwok/controllers/pod_controller_test.go
index e193f7dd..3aad94cd 100644
--- a/pkg/kwok/controllers/pod_controller_test.go
+++ b/pkg/kwok/controllers/pod_controller_test.go
@@ -167,7 +167,7 @@ func TestPodController(t *testing.T) {
 	podStages, _ := NewStagesFromYaml([]byte(stages.DefaultPodStages))
 	annotationSelector, _ := labels.Parse("fake=custom")
 	pods, err := NewPodController(PodControllerConfig{
-		ClientSet:                             clientset,
+		TypedClient:                           clientset,
 		NodeIP:                                defaultNodeIP,
 		CIDR:                                  defaultPodCIDR,
 		DisregardStatusWithAnnotationSelector: annotationSelector.String(),