Merge pull request #75 from skeeey/performance

add hub kubeconfig secret controller to replace mount secret directly
This commit is contained in:
OpenShift Merge Robot
2020-09-15 03:53:52 -04:00
committed by GitHub
16 changed files with 308 additions and 172 deletions
+4 -5
View File
@@ -28,13 +28,12 @@ spec:
- name: bootstrap-secret
mountPath: "/spoke/bootstrap"
readOnly: true
- name: hub-kubeconfig-secret
- name: hub-kubeconfig
mountPath: "/spoke/hub-kubeconfig"
readOnly: true
volumes:
- name: bootstrap-secret
secret:
secretName: bootstrap-secret
- name: hub-kubeconfig-secret
secret:
secretName: hub-kubeconfig-secret
- name: hub-kubeconfig
emptyDir:
medium: Memory
-1
View File
@@ -32,7 +32,6 @@ resources:
- ./role.yaml
- ./role_binding.yaml
- ./deployment.yaml
- ./secret.yaml
images:
- name: quay.io/open-cluster-management/registration:latest
-7
View File
@@ -1,7 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: hub-kubeconfig-secret
type: Opaque
data:
placeholder: YWRtaW4=
+18
View File
@@ -1,6 +1,9 @@
package testing
import (
"bytes"
"io/ioutil"
"os"
"reflect"
"testing"
@@ -184,3 +187,18 @@ func AssertLeaseUpdated(t *testing.T, lease, lastLease *coordinationv1.Lease) {
t.Errorf("expected lease updated, but failed")
}
}
// AssertFileExist asserts a given file exists
func AssertFileExist(t *testing.T, filePath string) {
if _, err := os.Stat(filePath); err != nil {
t.Errorf("expected file %q exits, but got error %v", filePath, err)
}
}
// AssertFileContent asserts a given file content
func AssertFileContent(t *testing.T, filePath string, expectedContent []byte) {
content, _ := ioutil.ReadFile(filePath)
if !bytes.Equal(content, expectedContent) {
t.Errorf("expect %v, but got %v", expectedContent, content)
}
}
-15
View File
@@ -5,7 +5,6 @@ import (
"crypto/tls"
"crypto/x509/pkix"
"fmt"
"reflect"
"time"
"github.com/openshift/library-go/pkg/controller/factory"
@@ -143,20 +142,6 @@ func (c *ClientCertForHubController) sync(ctx context.Context, syncCtx factory.S
return nil
}
// save the cluster name and agent name into secret if they are not saved yet
newSecretConfig := map[string][]byte{}
for k, v := range secret.Data {
newSecretConfig[k] = v
}
newSecretConfig[ClusterNameFile] = []byte(c.clusterName)
newSecretConfig[AgentNameFile] = []byte(c.agentName)
if !reflect.DeepEqual(newSecretConfig, secret.Data) {
secret.Data = newSecretConfig
if err := c.saveHubKubeconfigSecret(secret); err != nil {
return err
}
}
// create a csr to request new client certificate if
// a. there is no client certificate
// b. client certificate exists and has less than 20% of its life remaining
+2 -17
View File
@@ -3,7 +3,6 @@ package hubclientcert
import (
"context"
"fmt"
"reflect"
"testing"
"time"
@@ -11,7 +10,6 @@ import (
certificates "k8s.io/api/certificates/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
kubefake "k8s.io/client-go/kubernetes/fake"
@@ -48,21 +46,8 @@ func TestSync(t *testing.T) {
if _, ok := actual.(*certificates.CertificateSigningRequest); !ok {
t.Errorf("expected csr was created, but failed")
}
expectedSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: testSecretName,
},
Data: map[string][]byte{
ClusterNameFile: []byte(testinghelpers.TestManagedClusterName),
AgentNameFile: []byte(testAgentName),
},
}
testinghelpers.AssertActions(t, agentActions, "create")
actualSecret := agentActions[0].(clienttesting.CreateActionImpl).Object
if !reflect.DeepEqual(expectedSecret, actualSecret) {
t.Errorf("expected secret %v, but got %v", expectedSecret, actualSecret)
}
testinghelpers.AssertNoActions(t, agentActions)
},
},
{
@@ -0,0 +1,116 @@
package hubclientcert
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"os"
"path"
"github.com/openshift/library-go/pkg/controller/factory"
"github.com/openshift/library-go/pkg/operator/events"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
corev1informers "k8s.io/client-go/informers/core/v1"
corev1lister "k8s.io/client-go/listers/core/v1"
"k8s.io/klog/v2"
)
// hubKubeconfigSecretController watches the HubKubeconfig secret, if the secret is changed, this controller creates/updates the
// corresponding configuration files from the secret
type hubKubeconfigSecretController struct {
hubKubeconfigDir string
hubKubeconfigSecretNamespace string
hubKubeconfigSecretName string
spokeSecretLister corev1lister.SecretLister
}
// NewHubKubeconfigSecretController returns a new HubKubeconfigSecretController
func NewHubKubeconfigSecretController(
hubKubeconfigDir, hubKubeconfigSecretNamespace, hubKubeconfigSecretName string,
spokeSecretInformer corev1informers.SecretInformer,
recorder events.Recorder) factory.Controller {
s := &hubKubeconfigSecretController{
hubKubeconfigDir: hubKubeconfigDir,
hubKubeconfigSecretNamespace: hubKubeconfigSecretNamespace,
hubKubeconfigSecretName: hubKubeconfigSecretName,
spokeSecretLister: spokeSecretInformer.Lister(),
}
return factory.New().
WithInformersQueueKeyFunc(
func(obj runtime.Object) string {
accessor, err := meta.Accessor(obj)
if err != nil {
return ""
}
if accessor.GetNamespace() == hubKubeconfigSecretNamespace && accessor.GetName() == hubKubeconfigSecretName {
return accessor.GetName()
}
return ""
}, spokeSecretInformer.Informer()).
WithSync(s.sync).
ToController("HubKubeconfigSecretController", recorder)
}
func (s *hubKubeconfigSecretController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
hubKubeconfigSecretName := syncCtx.QueueKey()
if hubKubeconfigSecretName == "" {
return nil
}
klog.V(4).Infof("Reconciling Hub KubeConfig secret %q", hubKubeconfigSecretName)
secret, err := s.spokeSecretLister.Secrets(s.hubKubeconfigSecretNamespace).Get(hubKubeconfigSecretName)
if errors.IsNotFound(err) {
return nil
}
if err != nil {
return fmt.Errorf("unable to get secret %s/%s : %w", s.hubKubeconfigSecretNamespace, s.hubKubeconfigSecretName, err)
}
// if the secret is invalid, ignore it
if !hasValidKubeconfig(secret) {
return nil
}
if err := os.MkdirAll(s.hubKubeconfigDir, 0700); err != nil {
return fmt.Errorf("unable to create dir %q : %w", s.hubKubeconfigDir, err)
}
// create/update configuration files from the secret
for key, data := range secret.Data {
configFilePath := path.Join(s.hubKubeconfigDir, key)
if err := writeConfigFile(configFilePath, data, syncCtx.Recorder()); err != nil {
return fmt.Errorf("unable to write config file %q: %w", configFilePath, err)
}
}
return nil
}
// writeConfigFile creates or updates a specified file and record an event to log it.
func writeConfigFile(filename string, data []byte, recorder events.Recorder) error {
lastData, err := ioutil.ReadFile(filename)
if os.IsNotExist(err) {
if err := ioutil.WriteFile(path.Clean(filename), data, 0400); err != nil {
return err
}
recorder.Event("HubKubeConfigFileCreated", fmt.Sprintf("Hub config file %q is created from hub kubeconfig secret", filename))
return nil
}
if err != nil {
return err
}
if bytes.Equal(lastData, data) {
return nil
}
if err := ioutil.WriteFile(path.Clean(filename), data, 0400); err != nil {
return err
}
recorder.Event("HubKubeConfigFileUpdated", fmt.Sprintf("Hub config file %q is updated from hub kubeconfig secret", filename))
return nil
}
@@ -0,0 +1,140 @@
package hubclientcert
import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"testing"
"time"
testinghelpers "github.com/open-cluster-management/registration/pkg/helpers/testing"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/rand"
kubeinformers "k8s.io/client-go/informers"
kubefake "k8s.io/client-go/kubernetes/fake"
)
func TestHubKubeconfigSecretSync(t *testing.T) {
testDir, err := ioutil.TempDir("", "testhubkubeconfigsecretsync")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
defer os.RemoveAll(testDir)
kubeConfigFile := testinghelpers.NewKubeconfig(nil, nil)
cases := []struct {
name string
queueKey string
secret *corev1.Secret
oldConfigData map[string][]byte
validateFiles func(t *testing.T, fileDir string)
}{
{
name: "no secret",
queueKey: "",
validateFiles: func(t *testing.T, hubKubeconfigDir string) {
files, err := ioutil.ReadDir(hubKubeconfigDir)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(files) != 0 {
t.Errorf("expect no files, but get %d files", len(files))
}
},
},
{
name: "invalid secret",
queueKey: testSecretName,
secret: testinghelpers.NewHubKubeconfigSecret(testNamespace, testSecretName, "", nil, map[string][]byte{}),
validateFiles: func(t *testing.T, hubKubeconfigDir string) {
files, err := ioutil.ReadDir(hubKubeconfigDir)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if len(files) != 0 {
t.Errorf("expect no files, but get %d files", len(files))
}
},
},
{
name: "secret is created",
queueKey: testSecretName,
secret: testinghelpers.NewHubKubeconfigSecret(
testNamespace, testSecretName, "",
testinghelpers.NewTestCert("test", 60*time.Second),
map[string][]byte{
ClusterNameFile: []byte("test"),
AgentNameFile: []byte("test"),
KubeconfigFile: testinghelpers.NewKubeconfig(nil, nil),
},
),
validateFiles: func(t *testing.T, hubKubeconfigDir string) {
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, ClusterNameFile), []byte("test"))
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, AgentNameFile), []byte("test"))
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, KubeconfigFile), kubeConfigFile)
testinghelpers.AssertFileExist(t, path.Join(hubKubeconfigDir, TLSKeyFile))
testinghelpers.AssertFileExist(t, path.Join(hubKubeconfigDir, TLSCertFile))
},
},
{
name: "secret is updated",
queueKey: testSecretName,
oldConfigData: map[string][]byte{
ClusterNameFile: []byte("test"),
AgentNameFile: []byte("test"),
KubeconfigFile: []byte("test"),
},
secret: testinghelpers.NewHubKubeconfigSecret(
testNamespace, testSecretName, "",
testinghelpers.NewTestCert("test", 60*time.Second),
map[string][]byte{
ClusterNameFile: []byte("test1"),
AgentNameFile: []byte("test"),
KubeconfigFile: testinghelpers.NewKubeconfig(nil, nil),
},
),
validateFiles: func(t *testing.T, hubKubeconfigDir string) {
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, ClusterNameFile), []byte("test1"))
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, AgentNameFile), []byte("test"))
testinghelpers.AssertFileContent(t, path.Join(hubKubeconfigDir, KubeconfigFile), kubeConfigFile)
testinghelpers.AssertFileExist(t, path.Join(hubKubeconfigDir, TLSKeyFile))
testinghelpers.AssertFileExist(t, path.Join(hubKubeconfigDir, TLSCertFile))
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
kubeClient := kubefake.NewSimpleClientset()
kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Minute*10)
secretStore := kubeInformerFactory.Core().V1().Secrets().Informer().GetStore()
if c.secret != nil {
secretStore.Add(c.secret)
}
hubKubeconfigDir := path.Join(testDir, fmt.Sprintf("/%s/hub-kubeconfig", rand.String(6)))
if err := os.MkdirAll(hubKubeconfigDir, 0755); err != nil {
t.Errorf("unexpected error: %v", err)
}
for k, v := range c.oldConfigData {
testinghelpers.WriteFile(path.Join(hubKubeconfigDir, k), v)
}
ctrl := hubKubeconfigSecretController{
hubKubeconfigDir: hubKubeconfigDir,
hubKubeconfigSecretName: testSecretName,
hubKubeconfigSecretNamespace: testNamespace,
spokeSecretLister: kubeInformerFactory.Core().V1().Secrets().Lister(),
}
syncErr := ctrl.sync(context.TODO(), testinghelpers.NewFakeSyncContext(t, c.queueKey))
if syncErr != nil {
t.Errorf("unexpected err: %v", syncErr)
}
c.validateFiles(t, hubKubeconfigDir)
})
}
}
+7
View File
@@ -128,6 +128,13 @@ func (o *SpokeAgentOptions) RunSpokeAgent(ctx context.Context, controllerContext
)
go spokeClusterCreatingController.Run(ctx, 1)
hubKubeconfigSecretController := hubclientcert.NewHubKubeconfigSecretController(
o.HubKubeconfigDir, o.ComponentNamespace, o.HubKubeconfigSecret,
spokeKubeInformerFactory.Core().V1().Secrets(),
controllerContext.EventRecorder,
)
go hubKubeconfigSecretController.Run(ctx, 1)
// check if there already exists a valid client config for hub
ok, err := o.hasValidHubClientConfig()
if err != nil {
+4 -33
View File
@@ -7,7 +7,6 @@
// deploy/spoke/namespace.yaml
// deploy/spoke/role.yaml
// deploy/spoke/role_binding.yaml
// deploy/spoke/secret.yaml
// deploy/spoke/service_account.yaml
package bindata
@@ -149,16 +148,15 @@ spec:
- name: bootstrap-secret
mountPath: "/spoke/bootstrap"
readOnly: true
- name: hub-kubeconfig-secret
- name: hub-kubeconfig
mountPath: "/spoke/hub-kubeconfig"
readOnly: true
volumes:
- name: bootstrap-secret
secret:
secretName: bootstrap-secret
- name: hub-kubeconfig-secret
secret:
secretName: hub-kubeconfig-secret
- name: hub-kubeconfig
emptyDir:
medium: Memory
`)
func deploySpokeDeploymentYamlBytes() ([]byte, error) {
@@ -210,7 +208,6 @@ resources:
- ./role.yaml
- ./role_binding.yaml
- ./deployment.yaml
- ./secret.yaml
images:
- name: quay.io/open-cluster-management/registration:latest
@@ -315,30 +312,6 @@ func deploySpokeRole_bindingYaml() (*asset, error) {
return a, nil
}
var _deploySpokeSecretYaml = []byte(`apiVersion: v1
kind: Secret
metadata:
name: hub-kubeconfig-secret
type: Opaque
data:
placeholder: YWRtaW4=
`)
func deploySpokeSecretYamlBytes() ([]byte, error) {
return _deploySpokeSecretYaml, nil
}
func deploySpokeSecretYaml() (*asset, error) {
bytes, err := deploySpokeSecretYamlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "deploy/spoke/secret.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _deploySpokeService_accountYaml = []byte(`apiVersion: v1
kind: ServiceAccount
metadata:
@@ -419,7 +392,6 @@ var _bindata = map[string]func() (*asset, error){
"deploy/spoke/namespace.yaml": deploySpokeNamespaceYaml,
"deploy/spoke/role.yaml": deploySpokeRoleYaml,
"deploy/spoke/role_binding.yaml": deploySpokeRole_bindingYaml,
"deploy/spoke/secret.yaml": deploySpokeSecretYaml,
"deploy/spoke/service_account.yaml": deploySpokeService_accountYaml,
}
@@ -473,7 +445,6 @@ var _bintree = &bintree{nil, map[string]*bintree{
"namespace.yaml": {deploySpokeNamespaceYaml, map[string]*bintree{}},
"role.yaml": {deploySpokeRoleYaml, map[string]*bintree{}},
"role_binding.yaml": {deploySpokeRole_bindingYaml, map[string]*bintree{}},
"secret.yaml": {deploySpokeSecretYaml, map[string]*bintree{}},
"service_account.yaml": {deploySpokeService_accountYaml, map[string]*bintree{}},
}},
}},
+17 -37
View File
@@ -162,6 +162,23 @@ var _ = ginkgo.Describe("Loopback registration [development]", func() {
})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: nsName,
Name: "spoke-agent-sa",
},
}
err = wait.Poll(1*time.Second, 5*time.Second, func() (bool, error) {
var err error
_, err = hubClient.CoreV1().ServiceAccounts(nsName).Create(context.TODO(), sa, metav1.CreateOptions{})
if err != nil {
return false, err
}
return true, nil
})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
var (
deployment *unstructured.Unstructured
deploymentResource = schema.GroupVersionResource{
@@ -184,43 +201,6 @@ var _ = ginkgo.Describe("Loopback registration [development]", func() {
})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: nsName,
Name: "hub-kubeconfig-secret",
},
Data: map[string][]byte{
"placeholder": []byte("YWRtaW4="),
},
}
err = wait.Poll(1*time.Second, 5*time.Second, func() (bool, error) {
var err error
_, err = hubClient.CoreV1().Secrets(nsName).Create(context.TODO(), secret, metav1.CreateOptions{})
if err != nil {
return false, err
}
return true, nil
})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Namespace: nsName,
Name: "spoke-agent-sa",
},
}
err = wait.Poll(1*time.Second, 5*time.Second, func() (bool, error) {
var err error
_, err = hubClient.CoreV1().ServiceAccounts(nsName).Create(context.TODO(), sa, metav1.CreateOptions{})
if err != nil {
return false, err
}
return true, nil
})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
var (
csrs *certificatesv1beta1.CertificateSigningRequestList
csrClient = hubClient.CertificatesV1beta1().CertificateSigningRequests()
@@ -69,10 +69,6 @@ var _ = ginkgo.Describe("Certificate Rotation", func() {
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
// simulate k8s to mount the hub kubeconfig secret
err = util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
// the agent should rotate the certificate because the certificate with a short valid time
// the hub controller should auto approve it
gomega.Eventually(func() bool {
@@ -68,9 +68,6 @@ var _ = ginkgo.Describe("Cluster Lease Update", func() {
if _, err := util.GetFilledHubKubeConfigSecret(kubeClient, testNamespace, hubKubeconfigSecret); err != nil {
return false
}
if err := util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret); err != nil {
return false
}
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
@@ -123,9 +120,6 @@ var _ = ginkgo.Describe("Cluster Lease Update", func() {
if _, err := util.GetFilledHubKubeConfigSecret(kubeClient, testNamespace, hubKubeconfigSecret); err != nil {
return false
}
if err := util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret); err != nil {
return false
}
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
@@ -103,10 +103,6 @@ var _ = ginkgo.Describe("Agent Recovery", func() {
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
// simulate k8s to mount the hub kubeconfig secret
err = util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
// the spoke cluster should have joined condition finally
gomega.Eventually(func() bool {
spokeCluster, err := util.GetManagedCluster(clusterClient, managedClusterName)
@@ -197,10 +193,6 @@ var _ = ginkgo.Describe("Agent Recovery", func() {
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
// simulate k8s to mount the hub kubeconfig secret
err = util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
// the spoke cluster should have joined condition finally
gomega.Eventually(func() bool {
spokeCluster, err := util.GetManagedCluster(clusterClient, spokeClusterName)
@@ -100,10 +100,6 @@ var _ = ginkgo.Describe("Joining Process", func() {
return true
}, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue())
// simulate k8s to mount the hub kubeconfig secret
err = util.MountHubKubeConfigs(kubeClient, hubKubeconfigDir, testNamespace, hubKubeconfigSecret)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
// the spoke cluster should have joined condition finally
gomega.Eventually(func() bool {
spokeCluster, err := util.GetManagedCluster(clusterClient, managedClusterName)
-35
View File
@@ -307,41 +307,6 @@ func GetFilledHubKubeConfigSecret(kubeClient kubernetes.Interface, secretNamespa
return secret, nil
}
func MountHubKubeConfigs(kubeClient kubernetes.Interface, hubKubeConfigDir, secretNamespace, secretName string) error {
if _, err := os.Stat(hubKubeConfigDir); os.IsNotExist(err) {
if err = os.MkdirAll(hubKubeConfigDir, 0755); err != nil {
return err
}
}
secret, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(hubKubeConfigDir, "cluster-name"), secret.Data["cluster-name"], 0644); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(hubKubeConfigDir, "agent-name"), secret.Data["agent-name"], 0644); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(hubKubeConfigDir, "tls.crt"), secret.Data["tls.crt"], 0644); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(hubKubeConfigDir, "tls.key"), secret.Data["tls.key"], 0644); err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(hubKubeConfigDir, "kubeconfig"), secret.Data["kubeconfig"], 0644); err != nil {
return err
}
return nil
}
func FindUnapprovedSpokeCSR(kubeClient kubernetes.Interface, spokeClusterName string) (*certificates.CertificateSigningRequest, error) {
csrList, err := kubeClient.CertificatesV1beta1().CertificateSigningRequests().List(context.TODO(), metav1.ListOptions{
LabelSelector: fmt.Sprintf("open-cluster-management.io/cluster-name=%s", spokeClusterName),