diff --git a/pkg/controller/clusterpool/clusterpool_controller.go b/pkg/controller/clusterpool/clusterpool_controller.go index 32706150c9a..80d7b807412 100644 --- a/pkg/controller/clusterpool/clusterpool_controller.go +++ b/pkg/controller/clusterpool/clusterpool_controller.go @@ -11,6 +11,7 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" corev1 "k8s.io/api/core/v1" + 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/types" @@ -33,11 +34,13 @@ import ( ) const ( - ControllerName = hivev1.ClusterpoolControllerName - finalizer = "hive.openshift.io/clusters" - imageSetDependent = "cluster image set" - pullSecretDependent = "pull secret" - credentialsSecretDependent = "credentials secret" + ControllerName = hivev1.ClusterpoolControllerName + finalizer = "hive.openshift.io/clusters" + imageSetDependent = "cluster image set" + pullSecretDependent = "pull secret" + credentialsSecretDependent = "credentials secret" + clusterPoolAdminRoleName = "hive-cluster-pool-admin" + clusterPoolAdminRoleBindingName = "hive-cluster-pool-admin-binding" ) var ( @@ -111,9 +114,76 @@ func AddToManager(mgr manager.Manager, r *ReconcileClusterPool, concurrentReconc return err } + // Watch for changes to the hive cluster pool admin RoleBindings + if err := c.Watch( + &source.Kind{Type: &rbacv1.RoleBinding{}}, + &handler.EnqueueRequestsFromMapFunc{ + ToRequests: requestsForRBACResources(r.Client, r.logger), + }, + ); err != nil { + return err + } + + // Watch for changes to the hive-cluster-pool-admin-binding RoleBinding + if err := c.Watch( + &source.Kind{Type: &rbacv1.RoleBinding{}}, + &handler.EnqueueRequestsFromMapFunc{ + ToRequests: requestsForCDRBACResources(r.Client, clusterPoolAdminRoleBindingName, r.logger), + }, + ); err != nil { + return err + } + return nil } +func requestsForCDRBACResources(c client.Client, resourceName string, logger log.FieldLogger) handler.ToRequestsFunc { + return func(o handler.MapObject) []reconcile.Request { + if o.Meta.GetName() != resourceName { + return nil + } + clusterName := o.Meta.GetNamespace() + cd := &hivev1.ClusterDeployment{} + if err := c.Get(context.Background(), client.ObjectKey{Namespace: clusterName, Name: clusterName}, cd); err != nil { + logger.WithError(err).Log(controllerutils.LogLevel(err), "failed to get ClusterDeployment for RBAC resource") + return nil + } + cpKey := clusterPoolKey(cd) + if cpKey == nil { + return nil + } + return []reconcile.Request{{NamespacedName: *cpKey}} + } +} + +func requestsForRBACResources(c client.Client, logger log.FieldLogger) handler.ToRequestsFunc { + return func(o handler.MapObject) []reconcile.Request { + binding, ok := o.Object.(*rbacv1.RoleBinding) + if !ok { + return nil + } + if binding.RoleRef.Kind != "ClusterRole" || binding.RoleRef.Name != clusterPoolAdminRoleName { + return nil + } + + cpList := &hivev1.ClusterPoolList{} + if err := c.List(context.Background(), cpList, client.InNamespace(o.Meta.GetNamespace())); err != nil { + logger.WithError(err).Log(controllerutils.LogLevel(err), "failed to list cluster pools for RBAC resource") + return nil + } + var requests []reconcile.Request + for _, cpl := range cpList.Items { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: cpl.Namespace, + Name: cpl.Name, + }, + }) + } + return requests + } +} + var _ reconcile.Reconciler = &ReconcileClusterPool{} // ReconcileClusterPool reconciles a ClusterPool object @@ -231,9 +301,107 @@ func (r *ReconcileClusterPool) Reconcile(request reconcile.Request) (reconcile.R } } + if err := r.reconcileRBAC(clp, logger); err != nil { + log.WithError(err).Error("error reconciling RBAC") + return reconcile.Result{}, err + } + return reconcile.Result{}, nil } +func (r *ReconcileClusterPool) reconcileRBAC( + clp *hivev1.ClusterPool, + logger log.FieldLogger, +) error { + rbList := &rbacv1.RoleBindingList{} + if err := r.Client.List(context.Background(), rbList, client.InNamespace(clp.GetNamespace())); err != nil { + log.WithError(err).Error("error listing rolebindings") + return err + } + var subs []rbacv1.Subject + var roleRef rbacv1.RoleRef + for _, rb := range rbList.Items { + if rb.RoleRef.Kind != "ClusterRole" || + rb.RoleRef.Name != clusterPoolAdminRoleName { + continue + } + roleRef = rb.RoleRef + subs = append(subs, rb.Subjects...) + } + + if len(subs) == 0 { + // nothing to do here. + return nil + } + + // get namespaces where role needs to be bound. + cdNSList := &corev1.NamespaceList{} + if err := r.Client.List(context.Background(), cdNSList, + client.MatchingLabels{constants.ClusterPoolNameLabel: clp.GetName()}); err != nil { + log.WithError(err).Error("error listing namespaces for cluster pool") + return err + } + + // create role bindings. + var errs []error + for _, ns := range cdNSList.Items { + if ns.DeletionTimestamp != nil { + logger.WithField("namespace", ns.GetName()). + Debug("skipping syncing the rolebindings as the namespace is marked for deletion") + continue + } + rb := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.GetName(), + Name: clusterPoolAdminRoleBindingName, + }, + Subjects: subs, + RoleRef: roleRef, + } + orb := &rbacv1.RoleBinding{} + if err := r.applyRoleBinding(rb, orb, logger); err != nil { + errs = append(errs, errors.Wrapf(err, "failed to apply rolebinding in namespace %s", ns.GetNamespace())) + continue + } + } + if err := utilerrors.NewAggregate(errs); err != nil { + return err + } + return nil +} + +func (r *ReconcileClusterPool) applyRoleBinding(desired, observed *rbacv1.RoleBinding, logger log.FieldLogger) error { + key := client.ObjectKey{Namespace: desired.GetNamespace(), Name: desired.GetName()} + logger = logger.WithField("rolebinding", key) + + switch err := r.Client.Get(context.Background(), key, observed); { + case apierrors.IsNotFound(err): + logger.Info("creating rolebinding") + if err := r.Create(context.Background(), desired); err != nil { + logger.WithError(err).Error("could not create rolebinding") + return err + } + return nil + case err != nil: + logger.WithError(err).Error("error getting rolebinding") + return errors.Wrap(err, "could not get rolebinding") + } + + if reflect.DeepEqual(observed.Subjects, desired.Subjects) && reflect.DeepEqual(observed.RoleRef, desired.RoleRef) { + return nil + } + observed.Subjects = desired.Subjects + observed.RoleRef = desired.RoleRef + + logger.Info("updating rolebinding") + if err := r.Update(context.Background(), observed); err != nil { + logger.WithError(err).Error("could not update rolebinding") + return err + } + + return nil +} + func (r *ReconcileClusterPool) addClusters( clp *hivev1.ClusterPool, newClusterCount int, diff --git a/pkg/controller/clusterpool/clusterpool_controller_test.go b/pkg/controller/clusterpool/clusterpool_controller_test.go index 371014a1ca8..2981e8e3926 100644 --- a/pkg/controller/clusterpool/clusterpool_controller_test.go +++ b/pkg/controller/clusterpool/clusterpool_controller_test.go @@ -2,6 +2,7 @@ package clusterpool import ( "context" + "sort" "testing" "time" @@ -14,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -21,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" hivev1 "github.com/openshift/hive/pkg/apis/hive/v1" + "github.com/openshift/hive/pkg/constants" controllerutils "github.com/openshift/hive/pkg/controller/utils" testclaim "github.com/openshift/hive/pkg/test/clusterclaim" testcd "github.com/openshift/hive/pkg/test/clusterdeployment" @@ -40,6 +43,7 @@ func TestReconcileClusterPool(t *testing.T) { scheme := runtime.NewScheme() hivev1.AddToScheme(scheme) corev1.AddToScheme(scheme) + rbacv1.AddToScheme(scheme) poolBuilder := testcp.FullBuilder(testNamespace, testLeasePoolName, scheme). GenericOptions( @@ -519,3 +523,755 @@ func TestReconcileClusterPool(t *testing.T) { }) } } + +func TestReconcileRBAC(t *testing.T) { + scheme := runtime.NewScheme() + hivev1.AddToScheme(scheme) + corev1.AddToScheme(scheme) + rbacv1.AddToScheme(scheme) + + tests := []struct { + name string + + existing []runtime.Object + + expectedBindings []rbacv1.RoleBinding + expectedErr string + }{{ + name: "no binding referring to cluster role", + existing: []runtime.Object{}, + }, { + name: "no binding referring to cluster role", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "admin"}, + }, + }, + }, { + name: "binding referring to cluster role but no namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "binding referring to cluster role but no namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-namespace-1", + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "binding referring to cluster role but no namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-namespace-1", + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-namespace-2", + Labels: map[string]string{constants.ClusterPoolNameLabel: "some-other-pool"}, + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "binding referring to cluster role with one namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "binding referring to cluster role with multiple namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-2", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-2", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "multiple binding referring to cluster role with one namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "multiple binding referring to cluster role with multiple namespace for pool", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-2", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-2", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role binding that is same", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role bindings that are same", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role binding that are different", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role binding that are different", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role bindings that are different", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role bindings that are different", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}, {Kind: "Group", Name: "test-admin-group-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }, { + name: "pre existing role bindings that are different", + existing: []runtime.Object{ + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-1", + Labels: map[string]string{constants.ClusterPoolNameLabel: testLeasePoolName}, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-1"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + + expectedBindings: []rbacv1.RoleBinding{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test-cluster-1", + Name: "hive-cluster-pool-admin-binding", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}, {Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test", + }, + Subjects: []rbacv1.Subject{{Kind: "User", Name: "test-admin-another"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + Name: "test-2", + }, + Subjects: []rbacv1.Subject{{Kind: "Group", Name: "test-admin-group-1"}}, + RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: "hive-cluster-pool-admin"}, + }, + }, + }} + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeClient := fake.NewFakeClientWithScheme(scheme, test.existing...) + logger := log.New() + logger.SetLevel(log.DebugLevel) + controllerExpectations := controllerutils.NewExpectations(logger) + rcp := &ReconcileClusterPool{ + Client: fakeClient, + logger: logger, + expectations: controllerExpectations, + } + + err := rcp.reconcileRBAC(&hivev1.ClusterPool{ + ObjectMeta: metav1.ObjectMeta{ + Name: testLeasePoolName, + Namespace: testNamespace, + }, + }, logger) + if test.expectedErr == "" { + require.NoError(t, err) + + rbs := &rbacv1.RoleBindingList{} + err = fakeClient.List(context.Background(), rbs) + require.NoError(t, err) + sort.Slice(rbs.Items, func(i, j int) bool { + return rbs.Items[i].Namespace < rbs.Items[j].Namespace && rbs.Items[i].Name < rbs.Items[j].Name + }) + for idx := range rbs.Items { + rbs.Items[idx].TypeMeta = metav1.TypeMeta{} + rbs.Items[idx].ResourceVersion = "" + } + + assert.Equal(t, test.expectedBindings, rbs.Items) + } else { + require.Regexp(t, err, test.expectedErr) + } + }) + } +}