diff --git a/cmd/controller-manager/app/controllers.go b/cmd/controller-manager/app/controllers.go index 02d130e7b..56aa07f8b 100644 --- a/cmd/controller-manager/app/controllers.go +++ b/cmd/controller-manager/app/controllers.go @@ -160,7 +160,6 @@ func addControllers( fedWorkspaceCacheController, fedWorkspaceRoleCacheController, fedWorkspaceRoleBindingCacheController cache.Controller if multiClusterEnabled { - fedUserClient, err := util.NewResourceClient(client.Config(), &iamv1alpha2.FedUserResource) if err != nil { klog.Error(err) @@ -208,31 +207,42 @@ func addControllers( } userController := user.NewController(client.Kubernetes(), client.KubeSphere(), client.Config(), - kubesphereInformer.Iam().V1alpha2().Users(), fedUserCache, fedUserCacheController, + kubesphereInformer.Iam().V1alpha2().Users(), + fedUserCache, fedUserCacheController, kubernetesInformer.Core().V1().ConfigMaps(), ldapClient, multiClusterEnabled) - csrController := certificatesigningrequest.NewController(client.Kubernetes(), kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(), + csrController := certificatesigningrequest.NewController(client.Kubernetes(), + kubernetesInformer.Certificates().V1beta1().CertificateSigningRequests(), kubernetesInformer.Core().V1().ConfigMaps(), client.Config()) clusterRoleBindingController := clusterrolebinding.NewController(client.Kubernetes(), - kubernetesInformer.Rbac().V1().ClusterRoleBindings(), kubernetesInformer.Apps().V1().Deployments(), - kubernetesInformer.Core().V1().Pods(), kubesphereInformer.Iam().V1alpha2().Users()) + kubernetesInformer.Rbac().V1().ClusterRoleBindings(), + kubernetesInformer.Apps().V1().Deployments(), + kubernetesInformer.Core().V1().Pods(), + kubesphereInformer.Iam().V1alpha2().Users()) globalRoleController := globalrole.NewController(client.Kubernetes(), client.KubeSphere(), kubesphereInformer.Iam().V1alpha2().GlobalRoles(), fedGlobalRoleCache, fedGlobalRoleCacheController) workspaceRoleController := workspacerole.NewController(client.Kubernetes(), client.KubeSphere(), - kubesphereInformer.Iam().V1alpha2().WorkspaceRoles(), fedWorkspaceRoleCache, fedWorkspaceRoleCacheController) + kubesphereInformer.Iam().V1alpha2().WorkspaceRoles(), + fedWorkspaceRoleCache, fedWorkspaceRoleCacheController, + kubesphereInformer.Tenant().V1alpha2().WorkspaceTemplates(), multiClusterEnabled) globalRoleBindingController := globalrolebinding.NewController(client.Kubernetes(), client.KubeSphere(), - kubesphereInformer.Iam().V1alpha2().GlobalRoleBindings(), fedGlobalRoleBindingCache, fedGlobalRoleBindingCacheController, multiClusterEnabled) + kubesphereInformer.Iam().V1alpha2().GlobalRoleBindings(), + fedGlobalRoleBindingCache, fedGlobalRoleBindingCacheController, multiClusterEnabled) workspaceRoleBindingController := workspacerolebinding.NewController(client.Kubernetes(), client.KubeSphere(), - kubesphereInformer.Iam().V1alpha2().WorkspaceRoleBindings(), fedWorkspaceRoleBindingCache, fedWorkspaceRoleBindingCacheController) + kubesphereInformer.Iam().V1alpha2().WorkspaceRoleBindings(), + fedWorkspaceRoleBindingCache, fedWorkspaceRoleBindingCacheController, + kubesphereInformer.Tenant().V1alpha2().WorkspaceTemplates(), multiClusterEnabled) workspaceTemplateController := workspacetemplate.NewController(client.Kubernetes(), client.KubeSphere(), - kubesphereInformer.Tenant().V1alpha2().WorkspaceTemplates(), kubesphereInformer.Tenant().V1alpha1().Workspaces(), - kubesphereInformer.Iam().V1alpha2().RoleBases(), kubesphereInformer.Iam().V1alpha2().WorkspaceRoles(), + kubesphereInformer.Tenant().V1alpha2().WorkspaceTemplates(), + kubesphereInformer.Tenant().V1alpha1().Workspaces(), + kubesphereInformer.Iam().V1alpha2().RoleBases(), + kubesphereInformer.Iam().V1alpha2().WorkspaceRoles(), fedWorkspaceCache, fedWorkspaceCacheController, multiClusterEnabled) var clusterController manager.Runnable @@ -253,28 +263,32 @@ func addControllers( } nsnpController = nsnetworkpolicy.NewNSNetworkPolicyController(client.Kubernetes(), - client.KubeSphere().NetworkV1alpha1(), kubesphereInformer.Network().V1alpha1().NamespaceNetworkPolicies(), - kubernetesInformer.Core().V1().Services(), kubernetesInformer.Core().V1().Nodes(), + client.KubeSphere().NetworkV1alpha1(), + kubesphereInformer.Network().V1alpha1().NamespaceNetworkPolicies(), + kubernetesInformer.Core().V1().Services(), + kubernetesInformer.Core().V1().Nodes(), kubesphereInformer.Tenant().V1alpha1().Workspaces(), kubernetesInformer.Core().V1().Namespaces(), nsnpProvider) } controllers := map[string]manager.Runnable{ - "virtualservice-controller": vsController, - "destinationrule-controller": drController, - "application-controller": apController, - "job-controller": jobController, - "s2ibinary-controller": s2iBinaryController, - "s2irun-controller": s2iRunController, - "storagecapability-controller": storageCapabilityController, - "volumeexpansion-controller": volumeExpansionController, - "user-controller": userController, - "cluster-controller": clusterController, - "nsnp-controller": nsnpController, - "csr-controller": csrController, - "clusterrolebinding-controller": clusterRoleBindingController, - "globalrolebinding-controller": globalRoleBindingController, - "workspacetemplate-controller": workspaceTemplateController, + "virtualservice-controller": vsController, + "destinationrule-controller": drController, + "application-controller": apController, + "job-controller": jobController, + "s2ibinary-controller": s2iBinaryController, + "s2irun-controller": s2iRunController, + "storagecapability-controller": storageCapabilityController, + "volumeexpansion-controller": volumeExpansionController, + "user-controller": userController, + "cluster-controller": clusterController, + "nsnp-controller": nsnpController, + "csr-controller": csrController, + "clusterrolebinding-controller": clusterRoleBindingController, + "globalrolebinding-controller": globalRoleBindingController, + "workspacetemplate-controller": workspaceTemplateController, + "workspacerole-controller": workspaceRoleController, + "workspacerolebinding-controller": workspaceRoleBindingController, } if devopsClient != nil { @@ -285,8 +299,6 @@ func addControllers( if multiClusterEnabled { controllers["globalrole-controller"] = globalRoleController - controllers["workspacerole-controller"] = workspaceRoleController - controllers["workspacerolebinding-controller"] = workspaceRoleBindingController } for name, ctrl := range controllers { diff --git a/pkg/controller/globalrolebinding/globalrolebinding_controller.go b/pkg/controller/globalrolebinding/globalrolebinding_controller.go index f245e44e5..48f3b5351 100644 --- a/pkg/controller/globalrolebinding/globalrolebinding_controller.go +++ b/pkg/controller/globalrolebinding/globalrolebinding_controller.go @@ -97,11 +97,11 @@ func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface } klog.Info("Setting up event handlers") globalRoleBindingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: ctl.enqueueClusterRoleBinding, + AddFunc: ctl.enqueueGlobalRoleBinding, UpdateFunc: func(old, new interface{}) { - ctl.enqueueClusterRoleBinding(new) + ctl.enqueueGlobalRoleBinding(new) }, - DeleteFunc: ctl.enqueueClusterRoleBinding, + DeleteFunc: ctl.enqueueGlobalRoleBinding, }) return ctl } @@ -138,7 +138,7 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { return nil } -func (c *Controller) enqueueClusterRoleBinding(obj interface{}) { +func (c *Controller) enqueueGlobalRoleBinding(obj interface{}) { var key string var err error if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { diff --git a/pkg/controller/namespace/namespace_controller.go b/pkg/controller/namespace/namespace_controller.go index 7b4e1659c..990fb01f4 100644 --- a/pkg/controller/namespace/namespace_controller.go +++ b/pkg/controller/namespace/namespace_controller.go @@ -189,7 +189,7 @@ func (r *ReconcileNamespace) bindWorkspace(namespace *corev1.Namespace) error { // federated namespace not controlled by workspace if namespace.Labels[constants.KubefedManagedLabel] != "true" && !metav1.IsControlledBy(namespace, workspace) { - namespace.OwnerReferences = removeWorkspaceOwnerReferences(namespace.OwnerReferences) + namespace.OwnerReferences = nil if err := controllerutil.SetControllerReference(workspace, namespace, r.scheme); err != nil { klog.Error(err) return err @@ -204,17 +204,6 @@ func (r *ReconcileNamespace) bindWorkspace(namespace *corev1.Namespace) error { return nil } -func removeWorkspaceOwnerReferences(ownerReferences []metav1.OwnerReference) []metav1.OwnerReference { - for i := 0; i < len(ownerReferences); i++ { - owner := ownerReferences[i] - if owner.Kind == tenantv1alpha1.ResourceKindWorkspace { - ownerReferences = append(ownerReferences[:i], ownerReferences[i+1:]...) - i-- - } - } - return ownerReferences -} - func (r *ReconcileNamespace) deleteRouter(namespace string) error { routerName := constants.IngressControllerPrefix + namespace @@ -296,34 +285,62 @@ func (r *ReconcileNamespace) initRoles(namespace *corev1.Namespace) error { return nil } +func (r *ReconcileNamespace) resetNamespaceOwner(namespace *corev1.Namespace) error { + namespace = namespace.DeepCopy() + delete(namespace.Annotations, constants.CreatorAnnotationKey) + err := r.Update(context.Background(), namespace) + klog.V(4).Infof("update namespace after creator has been deleted") + return err +} + func (r *ReconcileNamespace) initCreatorRoleBinding(namespace *corev1.Namespace) error { - if creator := namespace.Annotations[constants.CreatorAnnotationKey]; creator != "" { - creatorRoleBinding := &rbacv1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", creator, iamv1alpha2.NamespaceAdmin), - Namespace: namespace.Name, - }, - RoleRef: rbacv1.RoleRef{ + creator := namespace.Annotations[constants.CreatorAnnotationKey] + if creator == "" { + return nil + } + + var user iamv1alpha2.User + err := r.Get(context.Background(), types.NamespacedName{Name: creator}, &user) + if err != nil { + // skip if user has been deleted + if errors.IsNotFound(err) { + return r.resetNamespaceOwner(namespace) + } + klog.Error(err) + return err + } + + // skip if user has been deleted + if !user.DeletionTimestamp.IsZero() { + return r.resetNamespaceOwner(namespace) + } + + creatorRoleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", creator, iamv1alpha2.NamespaceAdmin), + Labels: map[string]string{iamv1alpha2.UserReferenceLabel: creator}, + Namespace: namespace.Name, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: iamv1alpha2.ResourceKindRole, + Name: iamv1alpha2.NamespaceAdmin, + }, + Subjects: []rbacv1.Subject{ + { + Name: creator, + Kind: iamv1alpha2.ResourceKindUser, APIGroup: rbacv1.GroupName, - Kind: iamv1alpha2.ResourceKindRole, - Name: iamv1alpha2.NamespaceAdmin, - }, - Subjects: []rbacv1.Subject{ - { - Name: creator, - Kind: iamv1alpha2.ResourceKindUser, - APIGroup: rbacv1.GroupName, - }, }, + }, + } + err = r.Client.Create(context.Background(), creatorRoleBinding) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil } - err := r.Client.Create(context.Background(), creatorRoleBinding) - if err != nil { - if errors.IsAlreadyExists(err) { - return nil - } - klog.Error(err) - return err - } + klog.Error(err) + return err } return nil diff --git a/pkg/controller/user/user_controller.go b/pkg/controller/user/user_controller.go index 1137a7b02..3812f4e2f 100644 --- a/pkg/controller/user/user_controller.go +++ b/pkg/controller/user/user_controller.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -39,8 +40,8 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" kubespherescheme "kubesphere.io/kubesphere/pkg/client/clientset/versioned/scheme" - userinformer "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" - userlister "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" + iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models/kubeconfig" ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap" @@ -63,8 +64,8 @@ type Controller struct { k8sClient kubernetes.Interface ksClient kubesphere.Interface kubeconfig kubeconfig.Interface - userInformer userinformer.UserInformer - userLister userlister.UserLister + userInformer iamv1alpha2informers.UserInformer + userLister iamv1alpha2listers.UserLister userSynced cache.InformerSynced cmSynced cache.InformerSynced fedUserCache cache.Store @@ -83,8 +84,10 @@ type Controller struct { } func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, - config *rest.Config, userInformer userinformer.UserInformer, fedUserCache cache.Store, fedUserController cache.Controller, - configMapInformer corev1informers.ConfigMapInformer, ldapClient ldapclient.Interface, multiClusterEnabled bool) *Controller { + config *rest.Config, userInformer iamv1alpha2informers.UserInformer, + fedUserCache cache.Store, fedUserController cache.Controller, + configMapInformer corev1informers.ConfigMapInformer, + ldapClient ldapclient.Interface, multiClusterEnabled bool) *Controller { // Create event broadcaster // Add sample-controller types to the default Kubernetes Scheme so Events can be // logged for sample-controller types. @@ -266,6 +269,11 @@ func (c *Controller) reconcile(key string) error { return err } + if err = c.deleteRoleBindings(user); err != nil { + klog.Error(err) + return err + } + // remove our finalizer from the list and update it. user.Finalizers = sliceutil.RemoveString(user.ObjectMeta.Finalizers, func(item string) bool { return item == finalizer @@ -483,6 +491,46 @@ func (c *Controller) ldapSync(user *iamv1alpha2.User) error { } } +func (c *Controller) deleteRoleBindings(user *iamv1alpha2.User) error { + listOptions := metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(labels.Set{iamv1alpha2.UserReferenceLabel: user.Name}).String(), + } + deleteOptions := metav1.NewDeleteOptions(0) + + if err := c.ksClient.IamV1alpha2().GlobalRoleBindings(). + DeleteCollection(deleteOptions, listOptions); err != nil { + klog.Error(err) + return err + } + + if err := c.ksClient.IamV1alpha2().WorkspaceRoleBindings(). + DeleteCollection(deleteOptions, listOptions); err != nil { + klog.Error(err) + return err + } + + if err := c.k8sClient.RbacV1().ClusterRoleBindings(). + DeleteCollection(deleteOptions, listOptions); err != nil { + klog.Error(err) + return err + } + + if result, err := c.k8sClient.CoreV1().Namespaces().List(metav1.ListOptions{}); err != nil { + klog.Error(err) + return err + } else { + for _, namespace := range result.Items { + if err := c.k8sClient.RbacV1().RoleBindings(namespace.Name). + DeleteCollection(deleteOptions, listOptions); err != nil { + klog.Error(err) + return err + } + } + } + + return nil +} + func encrypt(password string) (string, error) { // when user is already mapped to another identity, password is empty by default // unable to log in directly until password reset diff --git a/pkg/controller/user/user_controller_test.go b/pkg/controller/user/user_controller_test.go index ce7ed4773..5455beed1 100644 --- a/pkg/controller/user/user_controller_test.go +++ b/pkg/controller/user/user_controller_test.go @@ -94,7 +94,11 @@ func (f *fixture) newController() (*Controller, ksinformers.SharedInformerFactor } } - c := NewController(f.k8sclient, f.ksclient, nil, ksinformers.Iam().V1alpha2().Users(), nil, nil, k8sinformers.Core().V1().ConfigMaps(), ldapClient, false) + c := NewController(f.k8sclient, f.ksclient, nil, + ksinformers.Iam().V1alpha2().Users(), + nil, nil, + k8sinformers.Core().V1().ConfigMaps(), + ldapClient, false) c.userSynced = alwaysReady c.recorder = &record.FakeRecorder{} diff --git a/pkg/controller/workspacerole/workspacerole.go b/pkg/controller/workspacerole/workspacerole.go index 9f300ba02..beb3aa94a 100644 --- a/pkg/controller/workspacerole/workspacerole.go +++ b/pkg/controller/workspacerole/workspacerole.go @@ -36,7 +36,9 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + tenantv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant/v1alpha2" iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" + tenantv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/tenant/v1alpha2" "kubesphere.io/kubesphere/pkg/constants" "reflect" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -52,13 +54,18 @@ const ( ) type Controller struct { + scheme *runtime.Scheme k8sClient kubernetes.Interface ksClient kubesphere.Interface workspaceRoleInformer iamv1alpha2informers.WorkspaceRoleInformer workspaceRoleLister iamv1alpha2listers.WorkspaceRoleLister workspaceRoleSynced cache.InformerSynced + workspaceTemplateInformer tenantv1alpha2informers.WorkspaceTemplateInformer + workspaceTemplateLister tenantv1alpha2listers.WorkspaceTemplateLister + workspaceTemplateSynced cache.InformerSynced fedWorkspaceRoleCache cache.Store fedWorkspaceRoleCacheController cache.Controller + multiClusterEnabled bool // workqueue is a rate limited work queue. This is used to queue work to be // processed instead of performing it as soon as a change happens. This // means we can ensure we only process a fixed amount of resources at a @@ -71,7 +78,7 @@ type Controller struct { } func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, workspaceRoleInformer iamv1alpha2informers.WorkspaceRoleInformer, - fedWorkspaceRoleCache cache.Store, fedWorkspaceRoleCacheController cache.Controller) *Controller { + fedWorkspaceRoleCache cache.Store, fedWorkspaceRoleCacheController cache.Controller, workspaceTemplateInformer tenantv1alpha2informers.WorkspaceTemplateInformer, multiClusterEnabled bool) *Controller { // Create event broadcaster // Add sample-controller types to the default Kubernetes Scheme so Events can be // logged for sample-controller types. @@ -89,6 +96,10 @@ func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface workspaceRoleSynced: workspaceRoleInformer.Informer().HasSynced, fedWorkspaceRoleCache: fedWorkspaceRoleCache, fedWorkspaceRoleCacheController: fedWorkspaceRoleCacheController, + workspaceTemplateInformer: workspaceTemplateInformer, + workspaceTemplateLister: workspaceTemplateInformer.Lister(), + workspaceTemplateSynced: workspaceTemplateInformer.Informer().HasSynced, + multiClusterEnabled: multiClusterEnabled, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "WorkspaceRole"), recorder: recorder, } @@ -113,7 +124,13 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { // Wait for the caches to be synced before starting workers klog.Info("Waiting for informer caches to sync") - if ok := cache.WaitForCacheSync(stopCh, c.workspaceRoleSynced, c.fedWorkspaceRoleCacheController.HasSynced); !ok { + synced := make([]cache.InformerSynced, 0) + synced = append(synced, c.workspaceRoleSynced, c.workspaceTemplateSynced) + if c.multiClusterEnabled { + synced = append(synced, c.fedWorkspaceRoleCacheController.HasSynced) + } + + if ok := cache.WaitForCacheSync(stopCh); !ok { return fmt.Errorf("failed to wait for caches to sync") } @@ -214,11 +231,18 @@ func (c *Controller) reconcile(key string) error { return err } - if err = c.multiClusterSync(workspaceRole); err != nil { + if err = c.bindWorkspace(workspaceRole); err != nil { klog.Error(err) return err } + if c.multiClusterEnabled { + if err = c.multiClusterSync(workspaceRole); err != nil { + klog.Error(err) + return err + } + } + c.recorder.Event(workspaceRole, corev1.EventTypeNormal, successSynced, messageResourceSynced) return nil } @@ -227,6 +251,40 @@ func (c *Controller) Start(stopCh <-chan struct{}) error { return c.Run(4, stopCh) } +func (c *Controller) bindWorkspace(workspaceRole *iamv1alpha2.WorkspaceRole) error { + + workspaceName := workspaceRole.Labels[constants.WorkspaceLabelKey] + + if workspaceName == "" { + return nil + } + + workspace, err := c.workspaceTemplateLister.Get(workspaceName) + + if err != nil { + // skip if workspace not found + if errors.IsNotFound(err) { + return nil + } + klog.Error(err) + return err + } + + if !metav1.IsControlledBy(workspaceRole, workspace) { + workspaceRole.OwnerReferences = nil + if err := controllerutil.SetControllerReference(workspace, workspaceRole, scheme.Scheme); err != nil { + klog.Error(err) + return err + } + _, err = c.ksClient.IamV1alpha2().WorkspaceRoles().Update(workspaceRole) + if err != nil { + klog.Error(err) + return err + } + } + return nil +} + func (c *Controller) multiClusterSync(workspaceRole *iamv1alpha2.WorkspaceRole) error { if err := c.ensureNotControlledByKubefed(workspaceRole); err != nil { diff --git a/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go b/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go index 78730684b..608ed14ae 100644 --- a/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go +++ b/pkg/controller/workspacerolebinding/workspacerolebinding_controller.go @@ -37,7 +37,9 @@ import ( iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2" kubesphere "kubesphere.io/kubesphere/pkg/client/clientset/versioned" iamv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/iam/v1alpha2" + tenantv1alpha2informers "kubesphere.io/kubesphere/pkg/client/informers/externalversions/tenant/v1alpha2" iamv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/iam/v1alpha2" + tenantv1alpha2listers "kubesphere.io/kubesphere/pkg/client/listers/tenant/v1alpha2" "kubesphere.io/kubesphere/pkg/constants" "reflect" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -60,6 +62,10 @@ type Controller struct { workspaceRoleBindingSynced cache.InformerSynced fedWorkspaceRoleBindingCache cache.Store fedWorkspaceRoleBindingCacheController cache.Controller + workspaceTemplateInformer tenantv1alpha2informers.WorkspaceTemplateInformer + workspaceTemplateLister tenantv1alpha2listers.WorkspaceTemplateLister + workspaceTemplateSynced cache.InformerSynced + multiClusterEnabled bool // workqueue is a rate limited work queue. This is used to queue work to be // processed instead of performing it as soon as a change happens. This // means we can ensure we only process a fixed amount of resources at a @@ -72,7 +78,7 @@ type Controller struct { } func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface, workspaceRoleBindingInformer iamv1alpha2informers.WorkspaceRoleBindingInformer, - fedWorkspaceRoleBindingCache cache.Store, fedWorkspaceRoleBindingCacheController cache.Controller) *Controller { + fedWorkspaceRoleBindingCache cache.Store, fedWorkspaceRoleBindingCacheController cache.Controller, workspaceTemplateInformer tenantv1alpha2informers.WorkspaceTemplateInformer, multiClusterEnabled bool) *Controller { // Create event broadcaster // Add sample-controller types to the default Kubernetes Scheme so Events can be // logged for sample-controller types. @@ -90,16 +96,20 @@ func NewController(k8sClient kubernetes.Interface, ksClient kubesphere.Interface workspaceRoleBindingSynced: workspaceRoleBindingInformer.Informer().HasSynced, fedWorkspaceRoleBindingCache: fedWorkspaceRoleBindingCache, fedWorkspaceRoleBindingCacheController: fedWorkspaceRoleBindingCacheController, + workspaceTemplateInformer: workspaceTemplateInformer, + workspaceTemplateLister: workspaceTemplateInformer.Lister(), + workspaceTemplateSynced: workspaceTemplateInformer.Informer().HasSynced, + multiClusterEnabled: multiClusterEnabled, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "WorkspaceRoleBinding"), recorder: recorder, } klog.Info("Setting up event handlers") workspaceRoleBindingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: ctl.enqueueClusterRoleBinding, + AddFunc: ctl.enqueueWorkspaceRoleBinding, UpdateFunc: func(old, new interface{}) { - ctl.enqueueClusterRoleBinding(new) + ctl.enqueueWorkspaceRoleBinding(new) }, - DeleteFunc: ctl.enqueueClusterRoleBinding, + DeleteFunc: ctl.enqueueWorkspaceRoleBinding, }) return ctl } @@ -114,7 +124,13 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { // Wait for the caches to be synced before starting workers klog.Info("Waiting for informer caches to sync") - if ok := cache.WaitForCacheSync(stopCh, c.workspaceRoleBindingSynced, c.fedWorkspaceRoleBindingCacheController.HasSynced); !ok { + synced := make([]cache.InformerSynced, 0) + synced = append(synced, c.workspaceRoleBindingSynced, c.workspaceTemplateSynced) + if c.multiClusterEnabled { + synced = append(synced, c.fedWorkspaceRoleBindingCacheController.HasSynced) + } + + if ok := cache.WaitForCacheSync(stopCh, synced...); !ok { return fmt.Errorf("failed to wait for caches to sync") } @@ -130,7 +146,7 @@ func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error { return nil } -func (c *Controller) enqueueClusterRoleBinding(obj interface{}) { +func (c *Controller) enqueueWorkspaceRoleBinding(obj interface{}) { var key string var err error if key, err = cache.MetaNamespaceKeyFunc(obj); err != nil { @@ -215,11 +231,18 @@ func (c *Controller) reconcile(key string) error { return err } - if err = c.multiClusterSync(workspaceRoleBinding); err != nil { + if err = c.bindWorkspace(workspaceRoleBinding); err != nil { klog.Error(err) return err } + if c.multiClusterEnabled { + if err = c.multiClusterSync(workspaceRoleBinding); err != nil { + klog.Error(err) + return err + } + } + c.recorder.Event(workspaceRoleBinding, corev1.EventTypeNormal, successSynced, messageResourceSynced) return nil } @@ -228,6 +251,40 @@ func (c *Controller) Start(stopCh <-chan struct{}) error { return c.Run(4, stopCh) } +func (c *Controller) bindWorkspace(workspaceRoleBinding *iamv1alpha2.WorkspaceRoleBinding) error { + + workspaceName := workspaceRoleBinding.Labels[constants.WorkspaceLabelKey] + + if workspaceName == "" { + return nil + } + + workspace, err := c.workspaceTemplateLister.Get(workspaceName) + + if err != nil { + // skip if workspace not found + if errors.IsNotFound(err) { + return nil + } + klog.Error(err) + return err + } + + if !metav1.IsControlledBy(workspaceRoleBinding, workspace) { + workspaceRoleBinding.OwnerReferences = nil + if err := controllerutil.SetControllerReference(workspace, workspaceRoleBinding, scheme.Scheme); err != nil { + klog.Error(err) + return err + } + _, err = c.ksClient.IamV1alpha2().WorkspaceRoleBindings().Update(workspaceRoleBinding) + if err != nil { + klog.Error(err) + return err + } + } + return nil +} + func (c *Controller) multiClusterSync(workspaceRoleBinding *iamv1alpha2.WorkspaceRoleBinding) error { if err := c.ensureNotControlledByKubefed(workspaceRoleBinding); err != nil { diff --git a/pkg/controller/workspacetemplate/workspacetemplate_controller.go b/pkg/controller/workspacetemplate/workspacetemplate_controller.go index 508380226..66d26a465 100644 --- a/pkg/controller/workspacetemplate/workspacetemplate_controller.go +++ b/pkg/controller/workspacetemplate/workspacetemplate_controller.go @@ -477,37 +477,64 @@ func (r *Controller) initRoles(workspace *tenantv1alpha2.WorkspaceTemplate) erro return nil } +func (r *Controller) resetWorkspaceOwner(workspace *tenantv1alpha2.WorkspaceTemplate) error { + workspace = workspace.DeepCopy() + workspace.Spec.Template.Spec.Manager = "" + _, err := r.ksClient.TenantV1alpha2().WorkspaceTemplates().Update(workspace) + klog.V(4).Infof("update workspace after manager has been deleted") + return err +} + func (r *Controller) initManagerRoleBinding(workspace *tenantv1alpha2.WorkspaceTemplate) error { - if manager := workspace.Spec.Template.Spec.Manager; manager != "" { + manager := workspace.Spec.Template.Spec.Manager + if manager == "" { + return nil + } - workspaceAdminRoleName := fmt.Sprintf(iamv1alpha2.WorkspaceAdminFormat, workspace.Name) + user, err := r.ksClient.IamV1alpha2().Users().Get(manager, metav1.GetOptions{}) + if err != nil { + // skip if user has been deleted + if errors.IsNotFound(err) { + return r.resetWorkspaceOwner(workspace) + } + klog.Error(err) + return err + } - managerRoleBinding := &iamv1alpha2.WorkspaceRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-%s", manager, workspaceAdminRoleName), - Labels: map[string]string{tenantv1alpha1.WorkspaceLabel: workspace.Name}, + // skip if user has been deleted + if !user.DeletionTimestamp.IsZero() { + return r.resetWorkspaceOwner(workspace) + } + + workspaceAdminRoleName := fmt.Sprintf(iamv1alpha2.WorkspaceAdminFormat, workspace.Name) + managerRoleBinding := &iamv1alpha2.WorkspaceRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s-%s", manager, workspaceAdminRoleName), + Labels: map[string]string{ + tenantv1alpha1.WorkspaceLabel: workspace.Name, + iamv1alpha2.UserReferenceLabel: manager, }, - RoleRef: rbacv1.RoleRef{ - APIGroup: iamv1alpha2.SchemeGroupVersion.Group, - Kind: iamv1alpha2.ResourceKindWorkspaceRole, - Name: workspaceAdminRoleName, - }, - Subjects: []rbacv1.Subject{ - { - Name: manager, - Kind: iamv1alpha2.ResourceKindUser, - APIGroup: rbacv1.GroupName, - }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: iamv1alpha2.SchemeGroupVersion.Group, + Kind: iamv1alpha2.ResourceKindWorkspaceRole, + Name: workspaceAdminRoleName, + }, + Subjects: []rbacv1.Subject{ + { + Name: manager, + Kind: iamv1alpha2.ResourceKindUser, + APIGroup: rbacv1.GroupName, }, + }, + } + _, err = r.ksClient.IamV1alpha2().WorkspaceRoleBindings().Create(managerRoleBinding) + if err != nil { + if errors.IsAlreadyExists(err) { + return nil } - _, err := r.ksClient.IamV1alpha2().WorkspaceRoleBindings().Create(managerRoleBinding) - if err != nil { - if errors.IsAlreadyExists(err) { - return nil - } - klog.Error(err) - return err - } + klog.Error(err) + return err } return nil diff --git a/pkg/utils/k8sutil/k8sutil.go b/pkg/utils/k8sutil/k8sutil.go index 58d6c1dad..b8d76fda0 100644 --- a/pkg/utils/k8sutil/k8sutil.go +++ b/pkg/utils/k8sutil/k8sutil.go @@ -28,12 +28,3 @@ func IsControlledBy(reference []metav1.OwnerReference, kind string, name string) } return false } - -func GetControlledWorkspace(reference []metav1.OwnerReference) string { - for _, ref := range reference { - if ref.Kind == "Workspace" { - return ref.Name - } - } - return "" -}