/* Copyright 2019 The KubeSphere Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package namespace import ( "fmt" "github.com/golang/glog" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "time" corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" coreinformers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/informers/rbac/v1" rbacinformers "k8s.io/client-go/informers/rbac/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) var log = logf.Log.WithName("namespace-controller") const threadiness = 2 var ( defaultRoles = []rbac.Role{ {ObjectMeta: metaV1.ObjectMeta{Name: "admin", Annotations: map[string]string{"creator": "system"}}, Rules: []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, {ObjectMeta: metaV1.ObjectMeta{Name: "operator", Annotations: map[string]string{"creator": "system"}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}, {Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "kubesphere.io", "account.kubesphere.io", "autoscaling"}, Resources: []string{"*"}}}}, {ObjectMeta: metaV1.ObjectMeta{Name: "viewer", Annotations: map[string]string{"creator": "system"}}, Rules: []rbac.PolicyRule{{Verbs: []string{"get", "list", "watch"}, APIGroups: []string{"*"}, Resources: []string{"*"}}}}, } ) type NamespaceController struct { clientset kubernetes.Interface namespaceInformer coreinformers.NamespaceInformer roleInformer v1.RoleInformer workqueue workqueue.RateLimitingInterface } func NewNamespaceController( clientset kubernetes.Interface, namespaceInformer coreinformers.NamespaceInformer, roleInformer rbacinformers.RoleInformer) *NamespaceController { controller := &NamespaceController{ clientset: clientset, namespaceInformer: namespaceInformer, roleInformer: roleInformer, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "namespaces"), } log.V(3).Info("setting up event handlers") namespaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: controller.handleObject, UpdateFunc: func(old, new interface{}) { newNamespace := new.(*corev1.Namespace) oldNamespace := old.(*corev1.Namespace) if newNamespace.ResourceVersion == oldNamespace.ResourceVersion { return } controller.handleObject(new) }, DeleteFunc: controller.handleObject, }) roleInformer.Lister() return controller } func (c *NamespaceController) Start(stopCh <-chan struct{}) error { defer utilruntime.HandleCrash() defer c.workqueue.ShutDown() log.V(3).Info("starting namespace controller") defer glog.Info("shutting down namespace controller") // Wait for the caches to be synced before starting workers log.Info("waiting for informer caches to sync") if ok := cache.WaitForCacheSync(stopCh, c.namespaceInformer.Informer().HasSynced, c.roleInformer.Informer().HasSynced); !ok { glog.Fatalf("controller exit with error: failed to wait for caches to sync") } log.V(3).Info("starting workers") for i := 0; i < threadiness; i++ { go wait.Until(c.runWorker, time.Second, stopCh) } <-stopCh return nil } func (c *NamespaceController) runWorker() { for c.processNextWorkItem() { } } func (c *NamespaceController) processNextWorkItem() bool { obj, shutdown := c.workqueue.Get() if shutdown { return false } err := func(obj interface{}) error { defer c.workqueue.Done(obj) var namespace string var ok bool if namespace, ok = obj.(string); !ok { c.workqueue.Forget(obj) utilruntime.HandleError(fmt.Errorf("expected string in workqueue but got %#v", obj)) return nil } if err := c.reconcile(namespace); err != nil { c.workqueue.AddRateLimited(namespace) return fmt.Errorf("error syncing '%s': %s, requeuing", namespace, err.Error()) } c.workqueue.Forget(obj) log.V(4).Info("successfully namespace synced ", "namespace", namespace) return nil }(obj) if err != nil { utilruntime.HandleError(err) return true } return true } func (c *NamespaceController) reconcile(name string) error { _, err := c.namespaceInformer.Lister().Get(name) // Handler delete event if errors.IsNotFound(err) { return nil } // Handler update or create event if err := c.checkAndCreateRoles(name); err != nil { return err } return nil } func (c *NamespaceController) handleObject(obj interface{}) { if namespace, ok := obj.(*corev1.Namespace); ok { c.workqueue.AddRateLimited(namespace.Name) } } // Create default roles func (c *NamespaceController) checkAndCreateRoles(namespace string) error { for _, role := range defaultRoles { _, err := c.roleInformer.Lister().Roles(namespace).Get(role.Name) if err != nil { if errors.IsNotFound(err) { r := role.DeepCopy() r.Namespace = namespace _, err = c.clientset.RbacV1().Roles(namespace).Create(r) if err != nil && !errors.IsAlreadyExists(err) { return err } } else { return err } } } return nil }