From aafd8c1103044a21078cbb7a169a1c12c3b353d6 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Sun, 8 Mar 2026 17:43:37 +0100 Subject: [PATCH 1/2] Add kubeadm cluster role --- .../internal/controllers/controller.go | 6 +- .../internal/controllers/controller_test.go | 12 + .../kubeadm/internal/controllers/upgrade.go | 20 +- .../internal/controllers/upgrade_test.go | 4 + .../kubeadm/internal/workload_cluster.go | 3 +- .../kubeadm/internal/workload_cluster_rbac.go | 56 +++- .../internal/workload_cluster_rbac_test.go | 285 +++++++----------- 7 files changed, 186 insertions(+), 200 deletions(-) diff --git a/controlplane/kubeadm/internal/controllers/controller.go b/controlplane/kubeadm/internal/controllers/controller.go index 60059e1a0057..030512c7fce9 100644 --- a/controlplane/kubeadm/internal/controllers/controller.go +++ b/controlplane/kubeadm/internal/controllers/controller.go @@ -506,8 +506,12 @@ func (r *KubeadmControlPlaneReconciler) reconcile(ctx context.Context, controlPl return ctrl.Result{Requeue: true}, nil } + parsedVersionForPermissions, err := semver.ParseTolerant(controlPlane.KCP.Spec.Version) + if err != nil { + return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version) + } // Ensure kubeadm role bindings for v1.18+ - if err := workloadCluster.AllowBootstrapTokensToGetNodes(ctx); err != nil { + if err := workloadCluster.EnsureKubeadmPermissions(ctx, parsedVersionForPermissions); err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to set role and role binding for kubeadm") } diff --git a/controlplane/kubeadm/internal/controllers/controller_test.go b/controlplane/kubeadm/internal/controllers/controller_test.go index b0478ba89d88..eb4348fd5d55 100644 --- a/controlplane/kubeadm/internal/controllers/controller_test.go +++ b/controlplane/kubeadm/internal/controllers/controller_test.go @@ -34,8 +34,11 @@ import ( "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/tools/clientcmd" @@ -3743,6 +3746,15 @@ func TestObjectsPendingDelete(t *testing.T) { // test utils. func newFakeClient(initObjs ...client.Object) client.Client { + // Use a new scheme to avoid side effects if multiple tests are sharing the same global scheme. + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + _ = apiextensionsv1.AddToScheme(scheme) + _ = clusterv1.AddToScheme(scheme) + _ = bootstrapv1.AddToScheme(scheme) + _ = controlplanev1.AddToScheme(scheme) return &fakeClient{ startTime: time.Now(), Client: fake.NewClientBuilder().WithObjects(initObjs...).WithStatusSubresource(&controlplanev1.KubeadmControlPlane{}).Build(), diff --git a/controlplane/kubeadm/internal/controllers/upgrade.go b/controlplane/kubeadm/internal/controllers/upgrade.go index 20a1589294a6..5fc0d3f5da1e 100644 --- a/controlplane/kubeadm/internal/controllers/upgrade.go +++ b/controlplane/kubeadm/internal/controllers/upgrade.go @@ -55,23 +55,9 @@ func (r *KubeadmControlPlaneReconciler) upgradeControlPlane( return ctrl.Result{}, errors.Wrapf(err, "failed to parse kubernetes version %q", controlPlane.KCP.Spec.Version) } - if err := workloadCluster.ReconcileKubeletRBACRole(ctx, parsedVersion); err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to reconcile the remote kubelet RBAC role") - } - - if err := workloadCluster.ReconcileKubeletRBACBinding(ctx, parsedVersion); err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to reconcile the remote kubelet RBAC binding") - } - - // Ensure kubeadm cluster role & bindings for v1.18+ - // as per https://github.com/kubernetes/kubernetes/commit/b117a928a6c3f650931bdac02a41fca6680548c4 - if err := workloadCluster.AllowBootstrapTokensToGetNodes(ctx); err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to set role and role binding for kubeadm") - } - - // Ensure kubeadm clusterRoleBinding for v1.29+ as per https://github.com/kubernetes/kubernetes/pull/121305 - if err := workloadCluster.AllowClusterAdminPermissions(ctx, parsedVersion); err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to set cluster-admin ClusterRoleBinding for kubeadm") + // Creates ClusterRoleBinding and ClusterRoles introduced by new versions of kubeadm. + if err := workloadCluster.EnsureKubeadmPermissions(ctx, parsedVersion); err != nil { + return ctrl.Result{}, errors.Wrap(err, "failed to update control plane: failed to set cluster-admin ClusterRoleBinding for kubeadm") } kubeadmCMMutators := make([]func(*bootstrapv1.ClusterConfiguration), 0) diff --git a/controlplane/kubeadm/internal/controllers/upgrade_test.go b/controlplane/kubeadm/internal/controllers/upgrade_test.go index 70e63419bd10..e88ea2c1e1cc 100644 --- a/controlplane/kubeadm/internal/controllers/upgrade_test.go +++ b/controlplane/kubeadm/internal/controllers/upgrade_test.go @@ -88,6 +88,9 @@ func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleUp(t *testing.T) { Management: &internal.Management{Client: env}, Workload: &fakeWorkloadCluster{ Status: internal.ClusterStatus{Nodes: 1}, + Workload: &internal.Workload{ + Client: env, + }, }, }, managementClusterUncached: &fakeManagementCluster{ @@ -229,6 +232,7 @@ func TestKubeadmControlPlaneReconciler_RolloutStrategy_ScaleDown(t *testing.T) { } fakeClient := newFakeClient(objs...) fmc.Reader = fakeClient + fmc.Workload.Workload = &internal.Workload{Client: fakeClient} r := &KubeadmControlPlaneReconciler{ Client: fakeClient, SecretCachingClient: fakeClient, diff --git a/controlplane/kubeadm/internal/workload_cluster.go b/controlplane/kubeadm/internal/workload_cluster.go index 048df78ff59f..485a05e5a8d9 100644 --- a/controlplane/kubeadm/internal/workload_cluster.go +++ b/controlplane/kubeadm/internal/workload_cluster.go @@ -113,8 +113,7 @@ type WorkloadCluster interface { UpdateCoreDNS(ctx context.Context, kcp *controlplanev1.KubeadmControlPlane, version semver.Version) error RemoveEtcdMemberForMachine(ctx context.Context, machine *clusterv1.Machine) error ForwardEtcdLeadership(ctx context.Context, machine *clusterv1.Machine, leaderCandidate *clusterv1.Machine) error - AllowBootstrapTokensToGetNodes(ctx context.Context) error - AllowClusterAdminPermissions(ctx context.Context, version semver.Version) error + EnsureKubeadmPermissions(ctx context.Context, version semver.Version) error UpdateClusterConfiguration(ctx context.Context, version semver.Version, mutators ...func(*bootstrapv1.ClusterConfiguration)) error // State recovery tasks. diff --git a/controlplane/kubeadm/internal/workload_cluster_rbac.go b/controlplane/kubeadm/internal/workload_cluster_rbac.go index 8fc6c74c38ba..2027e3295d9e 100644 --- a/controlplane/kubeadm/internal/workload_cluster_rbac.go +++ b/controlplane/kubeadm/internal/workload_cluster_rbac.go @@ -53,6 +53,14 @@ const ( // UnversionedKubeletConfigMapName defines base kubelet configuration ConfigMap for kubeadm >= 1.24. UnversionedKubeletConfigMapName = "kubelet-config" + // KubeletAPIAdminClusterRoleBindingName is the name of the ClusterRoleBinding for the apiserver kubelet client. + KubeletAPIAdminClusterRoleBindingName = "kubeadm:apiserver-kubelet-client" + + // KubeletAPIAdminClusterRoleName is the name of the built-in ClusterRole for kubelet API access. + KubeletAPIAdminClusterRoleName = "system:kubelet-api-admin" + + // APIServerKubeletClientCertCommonName defines kubelet client certificate common name (CN). + APIServerKubeletClientCertCommonName = "kube-apiserver-kubelet-client" ) // EnsureResource creates a resoutce if the target resource doesn't exist. If the resource exists already, this function will ignore the resource instead. @@ -73,15 +81,22 @@ func (w *Workload) EnsureResource(ctx context.Context, obj client.Object) error return nil } -// AllowClusterAdminPermissions creates ClusterRoleBinding rules to use the kubeadm:cluster-admins Cluster Role created in Kubeadm v1.29. -func (w *Workload) AllowClusterAdminPermissions(ctx context.Context, targetVersion semver.Version) error { - // We intentionally only parse major/minor/patch so that the subsequent code - // also already applies to pre-release versions of new releases. - // Do nothing for Kubernetes < 1.29. - if version.Compare(targetVersion, semver.Version{Major: 1, Minor: 29, Patch: 0}, version.WithoutPreReleases()) < 0 { +// EnsureKubeadmPermissions creates ClusterRoleBinding and ClusterRoles introduced by new versions of kubeadm. +func (w *Workload) EnsureKubeadmPermissions(ctx context.Context, targetVersion semver.Version) error { + // Note: this code mimics the changes that kubeadm upgrade is doing. + // Cluster API must run the corresponding code when the user are upgrading to the minor where kubeadm introduced the change, + // including also patch releases. This is why the upper bound is the minor where a change was introduced plus one. + // Also, Cluster API applies new cluster roles when upgrading to releases older than when the changes + // have been introduced to kubeadm, so upgrade will keep working also in case the changes are backported to older versions. + if version.Compare(targetVersion, semver.Version{Major: 1, Minor: 38, Patch: 0}, version.WithoutPreReleases()) >= 0 { return nil } - return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{ + + // Kubeadm added this role with K8s 1.29 when introducing + // a cleaner split between kubeadm:cluster-admins and system:masters. + // Rif https://github.com/kubernetes/kubernetes/pull/121305 + // This change can be dropped when the min supported Kubernetes version in Cluster API >= 1.30. + err := w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: ClusterAdminsGroupAndClusterRoleBinding, }, @@ -96,8 +111,31 @@ func (w *Workload) AllowClusterAdminPermissions(ctx context.Context, targetVersi Name: ClusterAdminsGroupAndClusterRoleBinding, }, }, - }, - ) + }) + if err != nil { + return err + } + + // Kubeadm introduced this role with K8s 1.37 when reducing + // the scope of the credential provided to the API server for accessing kubelet. + // Rif https://github.com/kubernetes/kubernetes/pull/138957. + // This change can be dropped when the min supported Kubernetes version in Cluster API >=1.38. + return w.EnsureResource(ctx, &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: KubeletAPIAdminClusterRoleName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.UserKind, + Name: APIServerKubeletClientCertCommonName, + }, + }, + }) } // AllowBootstrapTokensToGetNodes creates RBAC rules to allow Node Bootstrap Tokens to list nodes. diff --git a/controlplane/kubeadm/internal/workload_cluster_rbac_test.go b/controlplane/kubeadm/internal/workload_cluster_rbac_test.go index 666f2829063d..6e3af3e6a3ba 100644 --- a/controlplane/kubeadm/internal/workload_cluster_rbac_test.go +++ b/controlplane/kubeadm/internal/workload_cluster_rbac_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2026 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,213 +17,156 @@ limitations under the License. package internal import ( - "errors" "testing" "github.com/blang/semver/v4" + "github.com/google/go-cmp/cmp" . "github.com/onsi/gomega" rbacv1 "k8s.io/api/rbac/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) -func TestCluster_ReconcileKubeletRBACBinding_NoError(t *testing.T) { - type wantRBAC struct { - role ctrlclient.ObjectKey - roleBinding ctrlclient.ObjectKey - } +func TestEnsureKubeadmPermissions(t *testing.T) { tests := []struct { - name string - client ctrlclient.Client - version semver.Version - want *wantRBAC + name string + objs []client.Object + targetVersion semver.Version + wantObjs []client.Object }{ { - name: "creates role and role binding for Kubernetes/kubeadm < v1.24", - client: fake.NewClientBuilder().Build(), - version: semver.MustParse("1.23.3"), - want: &wantRBAC{ - role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, - roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, - }, - }, - { - name: "tolerates existing role binding for Kubernetes/kubeadm < v1.24", - client: fake.NewClientBuilder().WithObjects( - &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, RoleRef: rbacv1.RoleRef{ - Name: "kubeadm:kubelet-config-1.23", - }}, - &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, Rules: []rbacv1.PolicyRule{{ - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - ResourceNames: []string{"kubelet-config-1.23"}, - }}}, - ).Build(), - version: semver.MustParse("1.23.3"), - want: &wantRBAC{ - role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, - roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config-1.23"}, + name: "Add kubeadm:cluster-admins and kubeadm:apiserver-kubelet-client ClusterRoleBinding for K8s <= 1.37", + objs: nil, + targetVersion: semver.MustParse("1.37.5"), + wantObjs: []client.Object{ + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: "cluster-admin", + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.GroupKind, + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + }, + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: KubeletAPIAdminClusterRoleName, + }, + Subjects: []rbacv1.Subject{ + { + Kind: rbacv1.UserKind, + Name: APIServerKubeletClientCertCommonName, + }, + }, + }, }, }, { - name: "creates role and role binding for Kubernetes/kubeadm >= v1.24", - client: fake.NewClientBuilder().Build(), - version: semver.MustParse("1.24.0"), - want: &wantRBAC{ - role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, - roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, + name: "Ignore kubeadm:cluster-admins and kubeadm:apiserver-kubelet-client ClusterRoleBinding it they already exist for K8s <= 1.37 (kubeadm started adding those roles in patch versions)", + objs: []client.Object{ + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + // Intentionally using a different ClusterRoleBinding to check that it is not changed. + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, + // Intentionally using a different ClusterRoleBinding to check that it is not changed. + }, }, - }, - { - name: "creates role and role binding for Kubernetes/kubeadm >= v1.24 ignoring pre-release and build tags", - client: fake.NewClientBuilder().Build(), - version: semver.MustParse("1.24.0-alpha.1+xyz.1"), - want: &wantRBAC{ - role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, - roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, + targetVersion: semver.MustParse("1.37.0"), + wantObjs: []client.Object{ + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, + }, }, }, { - name: "tolerates existing role binding for Kubernetes/kubeadm >= v1.24", - client: fake.NewClientBuilder().WithObjects( - &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, RoleRef: rbacv1.RoleRef{ - Name: "kubeadm:kubelet-config", - }}, - &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, Rules: []rbacv1.PolicyRule{{ - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - ResourceNames: []string{"kubelet-config"}, - }}}, - ).Build(), - version: semver.MustParse("1.24.1"), - want: &wantRBAC{ - role: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, - roleBinding: ctrlclient.ObjectKey{Namespace: metav1.NamespaceSystem, Name: "kubeadm:kubelet-config"}, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - - c := &Workload{ - Client: tt.client, - } - g.Expect(c.ReconcileKubeletRBACBinding(ctx, tt.version)).To(Succeed()) - g.Expect(c.ReconcileKubeletRBACRole(ctx, tt.version)).To(Succeed()) - if tt.want != nil { - r := &rbacv1.Role{} - // Role exists - g.Expect(tt.client.Get(ctx, tt.want.role, r)).To(Succeed()) - // Role ensure grants for the KubeletConfig config map - g.Expect(r.Rules).To(BeComparableTo([]rbacv1.PolicyRule{ - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"configmaps"}, - ResourceNames: []string{generateKubeletConfigName(tt.version)}, + name: "Ignore kubeadm:cluster-admins and kubeadm:apiserver-kubelet-client ClusterRoleBinding it they already exist for K8s >= 1.38 (kubeadm should add those roles or KCP in a previous update)", + objs: []client.Object{ + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, }, - })) - // RoleBinding exists - b := &rbacv1.RoleBinding{} - // RoleBinding refers to the role - g.Expect(tt.client.Get(ctx, tt.want.roleBinding, b)).To(Succeed()) - g.Expect(b.RoleRef.Name).To(Equal(tt.want.role.Name)) - } - }) - } -} - -func TestCluster_ReconcileKubeletRBACBinding_Error(t *testing.T) { - tests := []struct { - name string - client ctrlclient.Client - }{ - { - name: "client fails to update an expected error or the role binding/role", - client: &fakeClient{ - createErr: errors.New(""), + // Intentionally using a different ClusterRoleBinding to check that it is not changed. + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, + // Intentionally using a different ClusterRoleBinding to check that it is not changed. + }, }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) - - c := &Workload{ - Client: tt.client, - } - g.Expect(c.ReconcileKubeletRBACBinding(ctx, semver.MustParse("1.12.3"))).NotTo(Succeed()) - g.Expect(c.ReconcileKubeletRBACRole(ctx, semver.MustParse("1.13.3"))).NotTo(Succeed()) - }) - } -} - -func TestCluster_AllowBootstrapTokensToGetNodes_NoError(t *testing.T) { - tests := []struct { - name string - client ctrlclient.Client - }{ - { - name: "role binding and role already exist", - client: &fakeClient{ - get: map[string]interface{}{ - GetNodesClusterRoleName: &rbacv1.ClusterRoleBinding{}, + targetVersion: semver.MustParse("1.38.0"), + wantObjs: []client.Object{ + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: ClusterAdminsGroupAndClusterRoleBinding, + }, + }, + &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeletAPIAdminClusterRoleBindingName, + }, }, }, }, { - name: "role binding and role don't exist", - client: &fakeClient{}, - }, - { - name: "create returns an already exists error", - client: &fakeClient{ - createErr: apierrors.NewAlreadyExists(schema.GroupResource{}, ""), - }, + name: "Do not add kubeadm:cluster-admins and kubeadm:apiserver-kubelet-client ClusterRoleBinding for K8s >= 1.38 (this should never happen, kubeadm should add those roles or KCP in a previous update)", + objs: nil, + targetVersion: semver.MustParse("1.38.0"), + wantObjs: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) + fakeClient := fake.NewClientBuilder().WithObjects(tt.objs...).Build() - c := &Workload{ - Client: tt.client, + w := &Workload{ + Client: fakeClient, } - g.Expect(c.AllowBootstrapTokensToGetNodes(ctx)).To(Succeed()) - }) - } -} + err := w.EnsureKubeadmPermissions(t.Context(), tt.targetVersion) + g.Expect(err).ToNot(HaveOccurred()) -func TestCluster_AllowBootstrapTokensToGetNodes_Error(t *testing.T) { - tests := []struct { - name string - client ctrlclient.Client - }{ - { - name: "client fails to retrieve an expected error or the cluster role binding/role", - client: &fakeClient{ - createErr: errors.New(""), - }, - }, - } + crbList := &rbacv1.ClusterRoleBindingList{} + err = fakeClient.List(t.Context(), crbList) + g.Expect(err).ToNot(HaveOccurred()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - g := NewWithT(t) + g.Expect(crbList.Items).To(HaveLen(len(tt.wantObjs))) + + for _, o := range tt.wantObjs { + obj := o.DeepCopyObject().(client.Object) + err := fakeClient.Get(t.Context(), client.ObjectKeyFromObject(obj), obj) + g.Expect(err).ToNot(HaveOccurred()) - c := &Workload{ - Client: tt.client, + o.SetResourceVersion(obj.GetResourceVersion()) + g.Expect(obj).To(Equal(o), cmp.Diff(obj, o)) } - g.Expect(c.AllowBootstrapTokensToGetNodes(ctx)).NotTo(Succeed()) }) } } From 4d6ebd11578c9b865b9b60388905595f25f22ac3 Mon Sep 17 00:00:00 2001 From: Abhay Date: Fri, 14 Aug 2026 16:47:16 +0530 Subject: [PATCH 2/2] fix: lint --- controlplane/kubeadm/internal/workload_cluster_rbac_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controlplane/kubeadm/internal/workload_cluster_rbac_test.go b/controlplane/kubeadm/internal/workload_cluster_rbac_test.go index 6e3af3e6a3ba..778d4a7c0c4c 100644 --- a/controlplane/kubeadm/internal/workload_cluster_rbac_test.go +++ b/controlplane/kubeadm/internal/workload_cluster_rbac_test.go @@ -150,18 +150,18 @@ func TestEnsureKubeadmPermissions(t *testing.T) { w := &Workload{ Client: fakeClient, } - err := w.EnsureKubeadmPermissions(t.Context(), tt.targetVersion) + err := w.EnsureKubeadmPermissions(ctx, tt.targetVersion) g.Expect(err).ToNot(HaveOccurred()) crbList := &rbacv1.ClusterRoleBindingList{} - err = fakeClient.List(t.Context(), crbList) + err = fakeClient.List(ctx, crbList) g.Expect(err).ToNot(HaveOccurred()) g.Expect(crbList.Items).To(HaveLen(len(tt.wantObjs))) for _, o := range tt.wantObjs { obj := o.DeepCopyObject().(client.Object) - err := fakeClient.Get(t.Context(), client.ObjectKeyFromObject(obj), obj) + err := fakeClient.Get(ctx, client.ObjectKeyFromObject(obj), obj) g.Expect(err).ToNot(HaveOccurred()) o.SetResourceVersion(obj.GetResourceVersion())