diff --git a/pkg/apis/v1alpha/workspaces/workspaces.go b/pkg/apis/v1alpha/workspaces/workspaces.go index 24ccec2f7..905de7bc0 100644 --- a/pkg/apis/v1alpha/workspaces/workspaces.go +++ b/pkg/apis/v1alpha/workspaces/workspaces.go @@ -11,8 +11,13 @@ import ( "k8s.io/kubernetes/pkg/util/slice" + "strconv" + + "regexp" + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models/iam" + "kubesphere.io/kubesphere/pkg/models/metrics" "kubesphere.io/kubesphere/pkg/models/workspaces" ) @@ -26,9 +31,13 @@ func Register(ws *restful.WebService, subPath string) { ws.Route(ws.GET(subPath + "/{name}").To(WorkspaceDetailHandler)) ws.Route(ws.PUT(subPath + "/{name}").To(WorkspaceEditHandler)) ws.Route(ws.GET(subPath + "/{workspace}/namespaces").To(UserNamespaceListHandler)) + ws.Route(ws.GET(subPath + "/{workspace}/members/{username}/namespaces").To(UserNamespaceListHandler)) ws.Route(ws.POST(subPath + "/{name}/namespaces").To(NamespaceCreateHandler)) ws.Route(ws.DELETE(subPath + "/{name}/namespaces/{namespace}").To(NamespaceDeleteHandler)) + ws.Route(ws.GET(subPath + "/{name}/namespaces/{namespace}").To(NamespaceCheckHandler)) + ws.Route(ws.GET("/namespaces/{namespace}").To(NamespaceCheckHandler)) ws.Route(ws.GET(subPath + "/{name}/devops").To(DevOpsProjectHandler)) + ws.Route(ws.GET(subPath + "/{name}/members/{username}/devops").To(DevOpsProjectHandler)) ws.Route(ws.POST(subPath + "/{name}/devops").To(DevOpsProjectCreateHandler)) ws.Route(ws.DELETE(subPath + "/{name}/devops/{id}").To(DevOpsProjectDeleteHandler)) @@ -171,10 +180,22 @@ func MembersRemoveHandler(req *restful.Request, resp *restful.Response) { resp.WriteHeaderAndEntity(http.StatusOK, constants.MessageResponse{Message: "success"}) } +func NamespaceCheckHandler(req *restful.Request, resp *restful.Response) { + namespace := req.PathParameter("namespace") + + exist, err := workspaces.NamespaceExistCheck(namespace) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) + return + } + + resp.WriteEntity(map[string]bool{"exist": exist}) +} + func NamespaceDeleteHandler(req *restful.Request, resp *restful.Response) { namespace := req.PathParameter("namespace") workspace := req.PathParameter("name") - //force := req.QueryParameter("force") err := workspaces.DeleteNamespace(workspace, namespace) @@ -223,26 +244,14 @@ func DevOpsProjectCreateHandler(req *restful.Request, resp *restful.Response) { return } - project, err := workspaces.CreateDevopsProject(username, devops) + project, err := workspaces.CreateDevopsProject(username, workspace, devops) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) return } - if project.ProjectId == nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: "project create failed"}) - } else { - err = workspaces.BindingDevopsProject(workspace, *project.ProjectId) - - if err != nil { - workspaces.DeleteDevopsProject(username, *project.ProjectId) - resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) - return - } - - resp.WriteEntity(project) - } + resp.WriteEntity(project) } @@ -285,15 +294,53 @@ func NamespaceCreateHandler(req *restful.Request, resp *restful.Response) { func DevOpsProjectHandler(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("name") + username := req.PathParameter("username") + keyword := req.QueryParameter("keyword") - devOpsProjects, err := workspaces.DevopsProjects(workspace) + if username == "" { + username = req.HeaderParameter(UserNameHeader) + } + + limit := 65535 + offset := 0 + orderBy := "createTime" + reverse := true + + if groups := regexp.MustCompile(`^limit=(\d+),page=(\d+)$`).FindStringSubmatch(req.QueryParameter("paging")); len(groups) == 3 { + limit, _ = strconv.Atoi(groups[1]) + page, _ := strconv.Atoi(groups[2]) + if page < 0 { + page = 1 + } + offset = (page - 1) * limit + } + + if groups := regexp.MustCompile(`^(createTime|name)$`).FindStringSubmatch(req.QueryParameter("order")); len(groups) == 2 { + orderBy = groups[1] + reverse = false + } + + if q := req.QueryParameter("reverse"); q != "" { + b, err := strconv.ParseBool(q) + if err == nil { + reverse = b + } + } + + total, devOpsProjects, err := workspaces.ListDevopsProjectsByUser(username, workspace, keyword, orderBy, reverse, limit, offset) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) return } - resp.WriteEntity(devOpsProjects) + result := constants.PageableResponse{} + result.TotalCount = total + result.Items = make([]interface{}, 0) + for _, n := range devOpsProjects { + result.Items = append(result.Items, n) + } + resp.WriteEntity(result) } func WorkspaceCreateHandler(req *restful.Request, resp *restful.Response) { @@ -402,10 +449,10 @@ func WorkspaceDetailHandler(req *restful.Request, resp *restful.Response) { // List all workspaces for the current user func UserWorkspaceListHandler(req *restful.Request, resp *restful.Response) { - + keyword := req.QueryParameter("keyword") username := req.HeaderParameter(UserNameHeader) - list, err := workspaces.ListByUser(username) + list, err := workspaces.ListWorkspaceByUser(username, keyword) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) @@ -416,16 +463,62 @@ func UserWorkspaceListHandler(req *restful.Request, resp *restful.Response) { } func UserNamespaceListHandler(req *restful.Request, resp *restful.Response) { + withMetrics, err := strconv.ParseBool(req.QueryParameter("metrics")) + + if err != nil { + withMetrics = false + } + + username := req.PathParameter("username") + keyword := req.QueryParameter("keyword") + if username == "" { + username = req.HeaderParameter(UserNameHeader) + } + limit := 65535 + offset := 0 + orderBy := "createTime" + reverse := true + + if groups := regexp.MustCompile(`^limit=(\d+),page=(\d+)$`).FindStringSubmatch(req.QueryParameter("paging")); len(groups) == 3 { + limit, _ = strconv.Atoi(groups[1]) + page, _ := strconv.Atoi(groups[2]) + if page < 0 { + page = 1 + } + offset = (page - 1) * limit + } + + if groups := regexp.MustCompile(`^(createTime|name)$`).FindStringSubmatch(req.QueryParameter("order")); len(groups) == 2 { + orderBy = groups[1] + reverse = false + } + + if q := req.QueryParameter("reverse"); q != "" { + b, err := strconv.ParseBool(q) + if err == nil { + reverse = b + } + } - username := req.HeaderParameter(UserNameHeader) workspaceName := req.PathParameter("workspace") - namespaces, err := workspaces.ListNamespaceByUser(workspaceName, username) + total, namespaces, err := workspaces.ListNamespaceByUser(workspaceName, username, keyword, orderBy, reverse, limit, offset) + + if withMetrics { + namespaces = metrics.GetNamespacesWithMetrics(namespaces) + } if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()}) return } - resp.WriteEntity(namespaces) + result := constants.PageableResponse{} + result.TotalCount = total + result.Items = make([]interface{}, 0) + for _, n := range namespaces { + result.Items = append(result.Items, n) + } + + resp.WriteEntity(result) } diff --git a/pkg/models/controllers/clusterrole_bindings.go b/pkg/models/controllers/clusterrole_bindings.go index 89bfd789f..a561d0804 100644 --- a/pkg/models/controllers/clusterrole_bindings.go +++ b/pkg/models/controllers/clusterrole_bindings.go @@ -19,10 +19,16 @@ package controllers import ( "time" + "fmt" + "regexp" + "github.com/golang/glog" "github.com/pkg/errors" + rbac "k8s.io/api/rbac/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" ) func (ctl *ClusterRoleBindingCtl) Name() string { @@ -43,10 +49,63 @@ func (ctl *ClusterRoleBindingCtl) total() int { return len(list) } +func (ctl *ClusterRoleBindingCtl) handleWorkspaceRoleChange(clusterRole *rbac.ClusterRoleBinding) { + if groups := regexp.MustCompile("^system:(\\w+):(admin|operator|viewer)$").FindStringSubmatch(clusterRole.Name); len(groups) == 3 { + workspace := groups[1] + go ctl.restNamespaceRoleBinding(workspace) + } +} + +func (ctl *ClusterRoleBindingCtl) restNamespaceRoleBinding(workspace string) { + selector := labels.SelectorFromSet(labels.Set{"kubesphere.io/workspace": workspace}) + namespaces, err := ctl.K8sClient.CoreV1().Namespaces().List(meta_v1.ListOptions{LabelSelector: selector.String()}) + + if err != nil { + glog.Warning("workspace roles sync failed", workspace, err) + return + } + + for _, namespace := range namespaces.Items { + pathJson := fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`, initTimeAnnotateKey, "") + _, err := ctl.K8sClient.CoreV1().Namespaces().Patch(namespace.Name, "application/strategic-merge-patch+json", []byte(pathJson)) + if err != nil { + glog.Warning("workspace roles sync failed", workspace, err) + return + } + } +} + func (ctl *ClusterRoleBindingCtl) initListerAndInformer() { informerFactory := informers.NewSharedInformerFactory(ctl.K8sClient, time.Second*resyncCircle) ctl.lister = informerFactory.Rbac().V1().ClusterRoleBindings().Lister() ctl.informer = informerFactory.Rbac().V1().ClusterRoleBindings().Informer() + ctl.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + }, + UpdateFunc: func(old, new interface{}) { + oldValue := old.(*rbac.ClusterRoleBinding) + newValue := new.(*rbac.ClusterRoleBinding) + if !subjectsCompile(oldValue.Subjects, newValue.Subjects) { + ctl.handleWorkspaceRoleChange(newValue) + } + }, + DeleteFunc: func(obj interface{}) { + + }, + }) +} + +func subjectsCompile(s1 []rbac.Subject, s2 []rbac.Subject) bool { + if len(s1) != len(s2) { + return false + } + + for i, v := range s1 { + if v.Name != s2[i].Name || v.Kind != s2[i].Kind { + return false + } + } + return true } func (ctl *ClusterRoleBindingCtl) CountWithConditions(conditions string) int { diff --git a/pkg/models/controllers/namespaces.go b/pkg/models/controllers/namespaces.go index 88355264b..bed8faabf 100644 --- a/pkg/models/controllers/namespaces.go +++ b/pkg/models/controllers/namespaces.go @@ -46,17 +46,19 @@ import ( const ( provider = "kubernetes" admin = "admin" - editor = "editor" + editor = "operator" viewer = "viewer" kubectlNamespace = constants.KubeSphereControlNamespace kubectlConfigKey = "config" openPitrixRuntimeAnnotateKey = "openpitrix_runtime" creatorAnnotateKey = "creator" + initTimeAnnotateKey = "kubesphere.io/init-time" + workspaceLabelKey = "kubesphere.io/workspace" ) var adminRules = []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}}} -var editorRules = []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch"}, Resources: []string{"*"}}} -var viewerRules = []rbac.PolicyRule{{Verbs: []string{"list", "get", "watch"}, APIGroups: []string{"", "apps", "extensions", "batch"}, Resources: []string{"*"}}} +var editorRules = []rbac.PolicyRule{{Verbs: []string{"*"}, APIGroups: []string{"", "apps", "extensions", "batch", "kubesphere.io", "account.kubesphere.io"}, Resources: []string{"*"}}} +var viewerRules = []rbac.PolicyRule{{Verbs: []string{"list", "get", "watch"}, APIGroups: []string{"", "apps", "extensions", "batch", "kubesphere.io", "account.kubesphere.io"}, Resources: []string{"*"}}} type runTime struct { RuntimeId string `json:"runtime_id"` @@ -131,15 +133,84 @@ func (ctl *NamespaceCtl) createOpRuntime(namespace string) ([]byte, error) { return makeHttpRequest("POST", url, string(body)) } -func (ctl *NamespaceCtl) createDefaultRoleBinding(ns, user string) error { +func (ctl *NamespaceCtl) createDefaultRoleBinding(namespace *v1.Namespace) error { - roleBinding := &rbac.RoleBinding{ObjectMeta: metaV1.ObjectMeta{Name: admin, Namespace: ns}, - Subjects: []rbac.Subject{{Name: user, Kind: rbac.UserKind}}, RoleRef: rbac.RoleRef{Kind: "Role", Name: admin}} + workspace := "" + creator := "" + if namespace.Annotations != nil { + creator = namespace.Annotations[creatorAnnotateKey] + } + if namespace.Labels != nil { + workspace = namespace.Labels[workspaceLabelKey] + } - _, err := ctl.K8sClient.RbacV1().RoleBindings(ns).Create(roleBinding) + adminBinding, err := ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Get(admin, metaV1.GetOptions{}) - if err != nil && !errors.IsAlreadyExists(err) { - glog.Error(err) + if err != nil { + if errors.IsNotFound(err) { + adminBinding = new(rbac.RoleBinding) + adminBinding.Name = admin + adminBinding.Namespace = namespace.Name + adminBinding.RoleRef = rbac.RoleRef{Kind: "Role", Name: admin} + } else { + return err + } + } + + adminBinding.Subjects = make([]rbac.Subject, 0) + + if creator != "" { + adminBinding.Subjects = append(adminBinding.Subjects, rbac.Subject{Name: creator, Kind: rbac.UserKind}) + } + + if workspace != "" { + workspaceAdmin, err := ctl.K8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:admin", workspace), metaV1.GetOptions{}) + if err != nil { + return err + } + adminBinding.Subjects = append(adminBinding.Subjects, workspaceAdmin.Subjects...) + } + + if adminBinding.ResourceVersion == "" { + _, err = ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Create(adminBinding) + } else { + _, err = ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Update(adminBinding) + } + + if err != nil { + return err + } + + viewerBinding, err := ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Get(viewer, metaV1.GetOptions{}) + + if err != nil { + if errors.IsNotFound(err) { + viewerBinding = new(rbac.RoleBinding) + viewerBinding.Name = viewer + viewerBinding.Namespace = namespace.Name + viewerBinding.RoleRef = rbac.RoleRef{Kind: "Role", Name: viewer} + } else { + return err + } + } + + viewerBinding.Subjects = make([]rbac.Subject, 0) + + if workspace != "" { + workspaceViewer, err := ctl.K8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:viewer", workspace), metaV1.GetOptions{}) + if err != nil { + return err + } + viewerBinding.Subjects = append(viewerBinding.Subjects, workspaceViewer.Subjects...) + } + + if viewerBinding.ResourceVersion == "" { + _, err = ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Create(viewerBinding) + } else { + _, err = ctl.K8sClient.RbacV1().RoleBindings(namespace.Name).Update(viewerBinding) + } + + if err != nil { return err } @@ -172,60 +243,44 @@ func (ctl *NamespaceCtl) createDefaultRole(ns string) error { return nil } -func (ctl *NamespaceCtl) createRoleAndRuntime(item v1.Namespace) { - var creator string - var runtime string - ns := item.Name - - if item.Annotations == nil { - creator = "" - runtime = "" - } else { - runtime = item.Annotations[openPitrixRuntimeAnnotateKey] - creator = item.Annotations[creatorAnnotateKey] +func (ctl *NamespaceCtl) createRoleAndRuntime(namespace *v1.Namespace) { + runtime := "" + initTime := "" + if namespace.Annotations != nil { + runtime = namespace.Annotations[openPitrixRuntimeAnnotateKey] + initTime = namespace.Annotations[initTimeAnnotateKey] } componentsNamespaces := []string{constants.KubeSystemNamespace, constants.OpenPitrixNamespace, constants.IstioNamespace, constants.KubeSphereNamespace} - if len(runtime) == 0 && !slice.ContainsString(componentsNamespaces, ns, nil) { - glog.Infoln("create runtime:", ns) - var runtimeCreateError error - resp, runtimeCreateError := ctl.createOpRuntime(ns) - - if runtimeCreateError == nil { - var runtime runTime - runtimeCreateError = json.Unmarshal(resp, &runtime) - if runtimeCreateError == nil { - - if item.Annotations == nil { - item.Annotations = make(map[string]string, 0) - } - - item.Annotations[openPitrixRuntimeAnnotateKey] = runtime.RuntimeId - _, runtimeCreateError = ctl.K8sClient.CoreV1().Namespaces().Update(&item) - - } - } + if runtime == "" && !slice.ContainsString(componentsNamespaces, namespace.Name, nil) { + glog.Infoln("create runtime:", namespace.Name) + _, runtimeCreateError := ctl.createOpRuntime(namespace.Name) if runtimeCreateError != nil { glog.Error("runtime create error:", runtimeCreateError) } + } - if len(creator) > 0 { - roleCreateError := ctl.createDefaultRole(ns) - glog.Infoln("create default role:", ns) - if roleCreateError == nil { - - roleBindingError := ctl.createDefaultRoleBinding(ns, creator) - glog.Infoln("create default role binding:", ns) - if roleBindingError != nil { - glog.Error("default role binding create error:", roleBindingError) - } - - } else { - glog.Error("default role create error:", roleCreateError) + if initTime == "" { + err := ctl.createDefaultRole(namespace.Name) + glog.Infoln("create default role:", namespace.Name) + if err == nil { + err = ctl.createDefaultRoleBinding(namespace) + glog.Infoln("create default role binding:", namespace.Name) + if err != nil { + glog.Error("default role binding create error:", err) } + } else { + glog.Error("default role create error:", err) + } + if err == nil { + pathJson := fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`, initTimeAnnotateKey, time.Now().UTC().Format("2006-01-02T15:04:05Z")) + _, err = ctl.K8sClient.CoreV1().Namespaces().Patch(namespace.Name, "application/strategic-merge-patch+json", []byte(pathJson)) + if err != nil { + glog.Error("annotations patch error init failed:", namespace.Name, err) + } } } } @@ -293,7 +348,7 @@ func (ctl *NamespaceCtl) createCephSecretAfterNewNs(item v1.Namespace) { } } -func (ctl *NamespaceCtl) generateObject(item v1.Namespace) *Namespace { +func (ctl *NamespaceCtl) generateObject(item *v1.Namespace) *Namespace { var displayName string if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { @@ -333,17 +388,17 @@ func (ctl *NamespaceCtl) sync(stopChan chan struct{}) { db = db.CreateTable(&Namespace{}) ctl.initListerAndInformer() - list, err := ctl.lister.List(labels.Everything()) - if err != nil { - glog.Error(err) - return - } + //list, err := ctl.lister.List(labels.Everything()) + //if err != nil { + // glog.Error(err) + // return + //} - for _, item := range list { - obj := ctl.generateObject(*item) - db.Create(obj) - ctl.createRoleAndRuntime(*item) - } + //for _, item := range list { + // obj := ctl.generateObject(item) + // db.Create(obj) + // ctl.createRoleAndRuntime(item) + //} ctl.informer.Run(stopChan) } @@ -369,16 +424,16 @@ func (ctl *NamespaceCtl) initListerAndInformer() { AddFunc: func(obj interface{}) { object := obj.(*v1.Namespace) - mysqlObject := ctl.generateObject(*object) + mysqlObject := ctl.generateObject(object) db.Create(mysqlObject) - ctl.createRoleAndRuntime(*object) + ctl.createRoleAndRuntime(object) ctl.createCephSecretAfterNewNs(*object) }, UpdateFunc: func(old, new interface{}) { object := new.(*v1.Namespace) - mysqlObject := ctl.generateObject(*object) + mysqlObject := ctl.generateObject(object) db.Save(mysqlObject) - ctl.createRoleAndRuntime(*object) + ctl.createRoleAndRuntime(object) }, DeleteFunc: func(obj interface{}) { var item Namespace @@ -386,7 +441,6 @@ func (ctl *NamespaceCtl) initListerAndInformer() { db.Where("name=?", object.Name).Find(&item) db.Delete(item) ctl.deleteOpRuntime(*object) - }, }) diff --git a/pkg/models/iam/iam.go b/pkg/models/iam/iam.go index e14235374..674abf1b1 100644 --- a/pkg/models/iam/iam.go +++ b/pkg/models/iam/iam.go @@ -14,6 +14,8 @@ import ( "k8s.io/apimachinery/pkg/labels" v12 "k8s.io/client-go/listers/rbac/v1" + "k8s.io/kubernetes/pkg/util/slice" + "kubesphere.io/kubesphere/pkg/client" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models/controllers" @@ -67,6 +69,7 @@ func GetUsers(names []string) ([]User, error) { return nil, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -94,6 +97,7 @@ func GetUser(name string) (*User, error) { return nil, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -228,7 +232,8 @@ func DeleteRoleBindings(username string) error { length2 := len(roleBinding.Subjects) if length2 == 0 { - k8s.RbacV1().RoleBindings(roleBinding.Namespace).Delete(roleBinding.Name, &meta_v1.DeleteOptions{}) + deletePolicy := meta_v1.DeletePropagationForeground + k8s.RbacV1().RoleBindings(roleBinding.Namespace).Delete(roleBinding.Name, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) } else if length2 < length1 { k8s.RbacV1().RoleBindings(roleBinding.Namespace).Update(&roleBinding) } @@ -248,7 +253,8 @@ func DeleteRoleBindings(username string) error { length2 := len(roleBinding.Subjects) if length2 == 0 { - k8s.RbacV1().ClusterRoleBindings().Delete(roleBinding.Name, &meta_v1.DeleteOptions{}) + deletePolicy := meta_v1.DeletePropagationForeground + k8s.RbacV1().ClusterRoleBindings().Delete(roleBinding.Name, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) } else if length2 < length1 { k8s.RbacV1().ClusterRoleBindings().Update(&roleBinding) } @@ -265,6 +271,21 @@ func GetRole(namespace string, name string) (*v1.Role, error) { } return role, nil } +func GetWorkspaceUsers(workspace string, role string) []string { + users := make([]string, 0) + clusterRoleBindingLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoleBindings].Lister().(v12.ClusterRoleBindingLister) + clusterRoleBinding, err := clusterRoleBindingLister.Get(fmt.Sprintf("system:%s:%s", workspace, role)) + if err != nil { + return users + } + + for _, s := range clusterRoleBinding.Subjects { + if s.Kind == v1.UserKind && !slice.ContainsString(users, s.Name, nil) { + users = append(users, s.Name) + } + } + return users +} func GetClusterRoleBindings(name string) ([]v1.ClusterRoleBinding, error) { k8s := client.NewK8sClient() @@ -370,7 +391,6 @@ func GetRoles(namespace string, username string) ([]v1.Role, error) { // Get cluster roles by username func GetClusterRoles(username string) ([]v1.ClusterRole, error) { - //TODO fix NPE clusterRoleBindingLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoleBindings].Lister().(v12.ClusterRoleBindingLister) clusterRoleLister := controllers.ResourceControllers.Controllers[controllers.ClusterRoles].Lister().(v12.ClusterRoleLister) clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything()) @@ -382,7 +402,7 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { roles := make([]v1.ClusterRole, 0) for _, roleBinding := range clusterRoleBindings { - for _, subject := range roleBinding.Subjects { + for i, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && subject.Name == username { if roleBinding.RoleRef.Kind == ClusterRoleKind { role, err := clusterRoleLister.Get(roleBinding.RoleRef.Name) @@ -398,7 +418,8 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { roles = append(roles, *role) break } else if apierrors.IsNotFound(err) { - glog.Warning(err) + roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...) + client.NewK8sClient().RbacV1().ClusterRoleBindings().Update(roleBinding) break } else { return nil, err @@ -411,76 +432,6 @@ func GetClusterRoles(username string) ([]v1.ClusterRole, error) { return roles, nil } -//func RuleValidate(rules []v1.PolicyRule, rule v1.PolicyRule) bool { -// -// for _, apiGroup := range rule.APIGroups { -// if len(rule.NonResourceURLs) == 0 { -// for _, resource := range rule.Resources { -// -// //if len(Rule.ResourceNames) == 0 { -// -// for _, verb := range rule.Verbs { -// if !verbValidate(rules, apiGroup, "", resource, "", verb) { -// return false -// } -// } -// -// //} else { -// // for _, resourceName := range Rule.ResourceNames { -// // for _, verb := range Rule.Verbs { -// // if !verbValidate(rules, apiGroup, "", resource, resourceName, verb) { -// // return false -// // } -// // } -// // } -// //} -// } -// } else { -// for _, nonResourceURL := range rule.NonResourceURLs { -// for _, verb := range rule.Verbs { -// if !verbValidate(rules, apiGroup, nonResourceURL, "", "", verb) { -// return false -// } -// } -// } -// } -// } -// return true -//} - -//func verbValidate(rules []v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, resourceName string, verb string) bool { -// for _, rule := range rules { -// -// if nonResourceURL == "" { -// if slice.ContainsString(rule.APIGroups, apiGroup, nil) || -// slice.ContainsString(rule.APIGroups, v1.APIGroupAll, nil) { -// if slice.ContainsString(rule.Verbs, verb, nil) || -// slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { -// if slice.ContainsString(rule.Resources, v1.ResourceAll, nil) { -// return true -// } else if slice.ContainsString(rule.Resources, resource, nil) { -// if len(rule.ResourceNames) > 0 { -// if slice.ContainsString(rule.ResourceNames, resourceName, nil) { -// return true -// } -// } else if resourceName == "" { -// return true -// } -// } -// } -// } -// -// } else if slice.ContainsString(rule.NonResourceURLs, nonResourceURL, nil) || -// slice.ContainsString(rule.NonResourceURLs, v1.NonResourceAll, nil) { -// if slice.ContainsString(rule.Verbs, verb, nil) || -// slice.ContainsString(rule.Verbs, v1.VerbAll, nil) { -// return true -// } -// } -// } -// return false -//} - func GetUserRules(username string) (map[string][]Rule, error) { items := make(map[string][]Rule, 0) diff --git a/pkg/models/iam/policy.go b/pkg/models/iam/policy.go index 7557134c4..8286242bd 100644 --- a/pkg/models/iam/policy.go +++ b/pkg/models/iam/policy.go @@ -60,9 +60,22 @@ var ( {Name: "edit", Rules: []v1.PolicyRule{ { - Verbs: []string{"update", "patch"}, + Verbs: []string{"*"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces"}, + }, { + Verbs: []string{"*"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/*"}, + }, + { + Verbs: []string{"*"}, + APIGroups: []string{"jenkins.kubesphere.io"}, + Resources: []string{"*"}, + }, { + Verbs: []string{"*"}, + APIGroups: []string{"devops.kubesphere.io"}, + Resources: []string{"*"}, }, }, }, @@ -83,7 +96,34 @@ var ( {Name: "view", Rules: []v1.PolicyRule{ { - Verbs: []string{"list"}, + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/members"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/members"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"patch", "update"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/members"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/members"}, }, @@ -97,7 +137,7 @@ var ( {Name: "view", Rules: []v1.PolicyRule{ { - Verbs: []string{"list"}, + Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/devops"}, }, @@ -124,7 +164,7 @@ var ( {Name: "delete", Rules: []v1.PolicyRule{ { - Verbs: []string{"delete", "deletecollection"}, + Verbs: []string{"delete"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/devops"}, }, @@ -138,7 +178,7 @@ var ( {Name: "view", Rules: []v1.PolicyRule{ { - Verbs: []string{"list"}, + Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/namespaces"}, }, @@ -165,7 +205,7 @@ var ( {Name: "delete", Rules: []v1.PolicyRule{ { - Verbs: []string{"delete", "deletecollection"}, + Verbs: []string{"delete"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/namespaces"}, }, @@ -173,31 +213,57 @@ var ( }, }, }, - { - Name: "registries", - Actions: []Action{ - {Name: "view"}, - {Name: "create"}, - {Name: "edit"}, - {Name: "delete"}, - }, - }, { Name: "organizations", Actions: []Action{ - {Name: "view"}, - {Name: "create"}, - {Name: "edit"}, - {Name: "delete"}, - }, + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"workspaces/organizations"}, + }, + }, + }, + {Name: "create", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"create"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"workspaces/organizations"}, + }, + }, + }, + {Name: "edit", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"update", "patch"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"workspaces/organizations"}, + }, + }, + }, + {Name: "delete", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"delete"}, + APIGroups: []string{"account.kubesphere.io"}, + Resources: []string{"workspaces/organizations"}, + }, + }, + }}, }, { Name: "roles", Actions: []Action{ - {Name: "view"}, - {Name: "create"}, - {Name: "edit"}, - {Name: "delete"}, + {Name: "view", + Rules: []v1.PolicyRule{ + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/roles"}, + }, + }}, }, }, } @@ -242,56 +308,6 @@ var ( }, }, }, - //{ - // Name: "projects", - // Actions: []Action{ - // {Name: "view", - // Rules: []v1.PolicyRule{ - // { - // Verbs: []string{"get", "watch", "list"}, - // APIGroups: []string{""}, - // Resources: []string{"namespaces"}, - // }, - // }, - // }, - // {Name: "create", - // Rules: []v1.PolicyRule{ - // { - // Verbs: []string{"create"}, - // APIGroups: []string{""}, - // Resources: []string{"namespaces"}, - // }, - // }, - // }, - // {Name: "edit", - // Rules: []v1.PolicyRule{ - // { - // Verbs: []string{"update", "patch"}, - // APIGroups: []string{""}, - // Resources: []string{"namespaces"}, - // }, - // }, - // }, - // {Name: "delete", - // Rules: []v1.PolicyRule{ - // { - // Verbs: []string{"delete", "deletecollection"}, - // APIGroups: []string{""}, - // Resources: []string{"namespaces"}, - // }, - // }, - // }, - // {Name: "members", - // Rules: []v1.PolicyRule{ - // { - // Verbs: []string{"get", "watch", "list", "create", "delete", "patch", "update"}, - // APIGroups: []string{"rbac.authorization.k8s.io"}, - // Resources: []string{"rolebindings", "roles"}, - // }, - // }, - // }, - // }, - //}, { Name: "accounts", Actions: []Action{ diff --git a/pkg/models/iam/types.go b/pkg/models/iam/types.go index c457dd7ec..f20248ec9 100644 --- a/pkg/models/iam/types.go +++ b/pkg/models/iam/types.go @@ -20,11 +20,9 @@ type SimpleRule struct { } type User struct { - Username string `json:"username"` - //UID string `json:"uid"` - Groups []string `json:"groups"` - Password string `json:"password,omitempty"` - //Extra map[string]interface{} `json:"extra"` + Username string `json:"username"` + Groups []string `json:"groups"` + Password string `json:"password,omitempty"` AvatarUrl string `json:"avatar_url"` Description string `json:"description"` Email string `json:"email"` diff --git a/pkg/models/metrics/namespaces.go b/pkg/models/metrics/namespaces.go new file mode 100644 index 000000000..93ad1283e --- /dev/null +++ b/pkg/models/metrics/namespaces.go @@ -0,0 +1,51 @@ +package metrics + +import ( + "net/url" + "strings" + + "k8s.io/api/core/v1" + + "kubesphere.io/kubesphere/pkg/client" +) + +func GetNamespacesWithMetrics(namespaces []*v1.Namespace) []*v1.Namespace { + var nsNameList []string + for i := range namespaces { + nsNameList = append(nsNameList, namespaces[i].Name) + } + nsFilter := "^(" + strings.Join(nsNameList, "|") + ")$" + var timeRelateParams = make(url.Values) + + params := client.MonitoringRequestParams{ + NsFilter: nsFilter, + Params: timeRelateParams, + QueryType: client.DefaultQueryType, + MetricsFilter: "namespace_cpu_usage|namespace_memory_usage_wo_cache|namespace_pod_count", + } + + rawMetrics := MonitorAllMetrics(¶ms, MetricLevelNamespace) + + for _, result := range rawMetrics.Results { + for _, data := range result.Data.Result { + metricDescMap, ok := data["metric"].(map[string]interface{}) + if ok { + if ns, exist := metricDescMap["namespace"]; exist { + timeAndValue, ok := data["value"].([]interface{}) + if ok && len(timeAndValue) == 2 { + for i := 0; i < len(namespaces); i++ { + if namespaces[i].Name == ns { + if namespaces[i].Annotations == nil { + namespaces[i].Annotations = make(map[string]string, 0) + } + namespaces[i].Annotations[result.MetricName] = timeAndValue[1].(string) + } + } + } + } + } + } + } + + return namespaces +} diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go index c08190d25..773ba708b 100644 --- a/pkg/models/workspaces/workspaces.go +++ b/pkg/models/workspaces/workspaces.go @@ -26,6 +26,8 @@ import ( "github.com/golang/glog" + "sort" + "kubesphere.io/kubesphere/pkg/client" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/models/controllers" @@ -54,6 +56,7 @@ func DeleteDevopsProject(username string, devops string) error { if err != nil { return err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { return err @@ -64,7 +67,7 @@ func DeleteDevopsProject(username string, devops string) error { return nil } -func CreateDevopsProject(username string, devops DevopsProject) (*DevopsProject, error) { +func CreateDevopsProject(username string, workspace string, devops DevopsProject) (*DevopsProject, error) { data, err := json.Marshal(devops) @@ -81,6 +84,7 @@ func CreateDevopsProject(username string, devops DevopsProject) (*DevopsProject, return nil, err } + defer result.Body.Close() data, err = ioutil.ReadAll(result.Body) if err != nil { @@ -99,21 +103,121 @@ func CreateDevopsProject(username string, devops DevopsProject) (*DevopsProject, return nil, err } + err = BindingDevopsProject(workspace, *project.ProjectId) + + if err != nil { + DeleteDevopsProject(username, *project.ProjectId) + return nil, err + } + + go createDefaultDevopsRoleBinding(workspace, project) + return &project, nil } -func ListNamespaceByUser(workspaceName string, username string) ([]*core.Namespace, error) { +func createDefaultDevopsRoleBinding(workspace string, project DevopsProject) { + admins := iam.GetWorkspaceUsers(workspace, "admin") + + for _, admin := range admins { + createDevopsRoleBinding(workspace, *project.ProjectId, admin, "maintainer") + } + + viewers := iam.GetWorkspaceUsers(workspace, "viewer") + + for _, viewer := range viewers { + createDevopsRoleBinding(workspace, *project.ProjectId, viewer, "reporter") + } +} + +func deleteDevopsRoleBinding(workspace string, projectId string, user string) { + projects := make([]string, 0) + + if projectId != "" { + projects = append(projects, projectId) + } else { + p, err := GetDevOpsProjects(workspace) + if err != nil { + glog.Warning("delete devops role binding failed", workspace, projectId, user) + return + } + projects = append(projects, p...) + } + + for _, project := range projects { + request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/api/v1alpha/projects/%s/members/%s", constants.DevopsAPIServer, project, user), nil) + request.Header.Add("X-Token-Username", "admin") + resp, err := http.DefaultClient.Do(request) + if err != nil || resp.StatusCode > 200 { + glog.Warning("delete devops role binding failed", workspace, project, user) + } + } +} + +func createDevopsRoleBinding(workspace string, projectId string, user string, role string) { + + projects := make([]string, 0) + + if projectId != "" { + projects = append(projects, projectId) + } else { + p, err := GetDevOpsProjects(workspace) + if err != nil { + glog.Warning("create devops role binding failed", workspace, projectId, user, role) + return + } + projects = append(projects, p...) + } + + for _, project := range projects { + data := []byte(fmt.Sprintf(`{"username":"%s","role":"%s"}`, user, role)) + request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/api/v1alpha/projects/%s/members", constants.DevopsAPIServer, project), bytes.NewReader(data)) + request.Header.Add("Content-Type", "application/json") + request.Header.Add("X-Token-Username", "admin") + resp, err := http.DefaultClient.Do(request) + if err != nil || resp.StatusCode > 200 { + glog.Warning(fmt.Sprintf("create devops role binding failed %s,%s,%s,%s", workspace, project, user, role)) + } + } +} + +func ListNamespaceByUser(workspaceName string, username string, keyword string, orderBy string, reverse bool, limit int, offset int) (int, []*core.Namespace, error) { namespaces, err := Namespaces(workspaceName) if err != nil { - return nil, err + return 0, nil, err } + if keyword != "" { + for i := 0; i < len(namespaces); i++ { + if !strings.Contains(namespaces[i].Name, keyword) { + namespaces = append(namespaces[:i], namespaces[i+1:]...) + i-- + } + } + } + + sort.Slice(namespaces, func(i, j int) bool { + switch orderBy { + case "name": + if reverse { + return namespaces[i].Name < namespaces[j].Name + } else { + return namespaces[i].Name > namespaces[j].Name + } + default: + if reverse { + return namespaces[i].CreationTimestamp.Time.After(namespaces[j].CreationTimestamp.Time) + } else { + return namespaces[i].CreationTimestamp.Time.Before(namespaces[j].CreationTimestamp.Time) + } + } + }) + clusterRoles, err := iam.GetClusterRoles(username) if err != nil { - return nil, err + return 0, nil, err } rules := make([]v1.PolicyRule, 0) @@ -124,13 +228,11 @@ func ListNamespaceByUser(workspaceName string, username string) ([]*core.Namespa namespacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{workspaceName}, Verbs: []string{"get"}, Resources: []string{"workspaces/namespaces"}} - if iam.RulesMatchesRequired(rules, namespacesManager) { - return namespaces, nil - } else { + if !iam.RulesMatchesRequired(rules, namespacesManager) { for i := 0; i < len(namespaces); i++ { roles, err := iam.GetRoles(namespaces[i].Name, username) if err != nil { - return nil, err + return 0, nil, err } rules := make([]v1.PolicyRule, 0) for _, role := range roles { @@ -143,7 +245,13 @@ func ListNamespaceByUser(workspaceName string, username string) ([]*core.Namespa } } - return namespaces, nil + if len(namespaces) < offset { + return len(namespaces), namespaces, nil + } else if len(namespaces) < limit+offset { + return len(namespaces), namespaces[offset:], nil + } else { + return len(namespaces), namespaces[offset : limit+offset], nil + } } func Namespaces(workspaceName string) ([]*core.Namespace, error) { @@ -202,6 +310,7 @@ func Delete(workspace *Workspace) error { return err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -225,7 +334,7 @@ func release(workspace *Workspace) error { for _, devops := range workspace.DevopsProjects { err := DeleteDevopsProject(workspace.Creator, devops) - if err != nil { + if err != nil && !strings.Contains(err.Error(), "not found") { return err } } @@ -270,7 +379,7 @@ func Create(workspace *Workspace) (*Workspace, error) { if err != nil { return nil, err } - + defer result.Body.Close() data, err = ioutil.ReadAll(result.Body) if err != nil { @@ -319,6 +428,7 @@ func Edit(workspace *Workspace) (*Workspace, error) { return nil, err } + defer result.Body.Close() data, err = ioutil.ReadAll(result.Body) if err != nil { @@ -348,6 +458,7 @@ func Detail(name string) (*Workspace, error) { return nil, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -379,7 +490,7 @@ func Detail(name string) (*Workspace, error) { } // List all workspaces for the current user -func ListByUser(username string) ([]*Workspace, error) { +func ListWorkspaceByUser(username string, keyword string) ([]*Workspace, error) { clusterRoles, err := iam.GetClusterRoles(username) @@ -395,24 +506,31 @@ func ListByUser(username string) ([]*Workspace, error) { workspacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, Verbs: []string{"list", "get"}, Resources: []string{"workspaces"}} + var workspaces []*Workspace if iam.RulesMatchesRequired(rules, workspacesManager) { - return fetch(nil) + workspaces, err = fetch(nil) } else { workspaceNames := make([]string, 0) - for _, clusterRole := range clusterRoles { - if regexp.MustCompile("^system:\\w+:(admin|operator|viewer)$").MatchString(clusterRole.Name) { - arr := strings.Split(clusterRole.Name, ":") - workspaceNames = append(workspaceNames, arr[1]) + if groups := regexp.MustCompile(`^system:(\w+):(admin|operator|viewer)$`).FindStringSubmatch(clusterRole.Name); len(groups) == 3 { + if !slice.ContainsString(workspaceNames, groups[1], nil) { + workspaceNames = append(workspaceNames, groups[1]) + } } } - - if len(workspaceNames) == 0 { - return make([]*Workspace, 0), nil - } - - return fetch(workspaceNames) + workspaces, err = fetch(workspaceNames) } + + if keyword != "" { + for i := 0; i < len(workspaces); i++ { + if !strings.Contains(workspaces[i].Name, keyword) { + workspaces = append(workspaces[:i], workspaces[i+1:]...) + i-- + } + } + } + + return workspaces, err } func fetch(names []string) ([]*Workspace, error) { @@ -420,7 +538,11 @@ func fetch(names []string) ([]*Workspace, error) { url := fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer) if names != nil { - url = url + "?path=" + strings.Join(names, ",") + if len(names) == 0 { + return make([]*Workspace, 0), nil + } else { + url = url + "?path=" + strings.Join(names, ",") + } } result, err := http.Get(url) @@ -429,6 +551,7 @@ func fetch(names []string) ([]*Workspace, error) { return nil, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -463,7 +586,7 @@ func fetch(names []string) ([]*Workspace, error) { return workspaces, nil } -func DevopsProjects(workspace string) ([]DevopsProject, error) { +func ListDevopsProjectsByUser(username string, workspace string, keyword string, orderBy string, reverse bool, limit int, offset int) (int, []DevopsProject, error) { db := client.NewSharedDBClient() defer db.Close() @@ -471,48 +594,89 @@ func DevopsProjects(workspace string) ([]DevopsProject, error) { var workspaceDOPBindings []WorkspaceDPBinding if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil { - return nil, err + return 0, nil, err } devOpsProjects := make([]DevopsProject, 0) - for _, workspaceDOPBinding := range workspaceDOPBindings { - request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, workspaceDOPBinding.DevOpsProject), nil) - request.Header.Add("X-Token-Username", "admin") + request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects", constants.DevopsAPIServer), nil) + request.Header.Add("X-Token-Username", username) - result, err := http.DefaultClient.Do(request) - if err != nil { - return nil, err - } - data, err := ioutil.ReadAll(result.Body) + result, err := http.DefaultClient.Do(request) + if err != nil { + return 0, nil, err + } + defer result.Body.Close() + data, err := ioutil.ReadAll(result.Body) - if err != nil { - return nil, err - } - - if result.StatusCode == 403 || result.StatusCode == 404 { - if err := db.Delete(&workspaceDOPBinding).Error; err != nil { - return nil, err - } - continue - } - - if result.StatusCode > 200 { - return nil, ksErr.Wrap(data) - } - - var project DevopsProject - - err = json.Unmarshal(data, &project) - - if err != nil { - return nil, err - } - devOpsProjects = append(devOpsProjects, project) + if err != nil { + return 0, nil, err } - return devOpsProjects, nil + //if result.StatusCode == 403 || result.StatusCode == 404 { + // if err := db.Delete(&workspaceDOPBinding).Error; err != nil { + // return nil, err + // } + // continue + //} + if result.StatusCode > 200 { + return 0, nil, ksErr.Wrap(data) + } + + err = json.Unmarshal(data, &devOpsProjects) + + if err != nil { + return 0, nil, err + } + + if keyword != "" { + for i := 0; i < len(devOpsProjects); i++ { + if !strings.Contains(devOpsProjects[i].Name, keyword) { + devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...) + i-- + } + } + } + + sort.Slice(devOpsProjects, func(i, j int) bool { + switch orderBy { + case "name": + if reverse { + return devOpsProjects[i].Name < devOpsProjects[j].Name + } else { + return devOpsProjects[i].Name > devOpsProjects[j].Name + } + default: + if reverse { + return devOpsProjects[i].CreateTime.After(*devOpsProjects[j].CreateTime) + } else { + return devOpsProjects[i].CreateTime.Before(*devOpsProjects[j].CreateTime) + } + } + }) + + for i := 0; i < len(devOpsProjects); i++ { + inWorkspace := false + + for _, binding := range workspaceDOPBindings { + if binding.DevOpsProject == *devOpsProjects[i].ProjectId { + inWorkspace = true + } + } + if !inWorkspace { + devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...) + i-- + } + } + + if len(devOpsProjects) < offset { + return len(devOpsProjects), devOpsProjects, nil + } else if len(devOpsProjects) < limit+offset { + return len(devOpsProjects), devOpsProjects[offset:], nil + } else { + return len(devOpsProjects), devOpsProjects[offset : limit+offset], nil + } } func convertGroupToWorkspace(db *gorm.DB, group Group) (*Workspace, error) { namespaces, err := Namespaces(group.Name) @@ -584,6 +748,19 @@ func Invite(workspaceName string, users []UserInvite) error { return nil } +func NamespaceExistCheck(namespaceName string) (bool, error) { + _, err := client.NewK8sClient().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{}) + + if err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } else { + return false, err + } + } + return true, nil +} + func RemoveMembers(workspaceName string, users []string) error { workspace, err := Detail(workspaceName) @@ -644,6 +821,7 @@ func GetWorkspaceMembers(workspace string) ([]iam.User, error) { return nil, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { @@ -673,133 +851,33 @@ func WorkspaceRoleInit(workspace *Workspace) error { admin.Name = fmt.Sprintf("system:%s:admin", workspace.Name) admin.Kind = iam.ClusterRoleKind admin.Rules = []v1.PolicyRule{ - // apis/kubesphere.io/v1alpha1/workspaces/sample - // apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces - // apis/kubesphere.io/v1alpha1/workspaces/sample/devops - // apis/kubesphere.io/v1alpha1/workspaces/sample/roles - // apis/kubesphere.io/v1alpha1/workspaces/sample/members - // apis/kubesphere.io/v1alpha1/workspaces/sample/members/admin - { Verbs: []string{"*"}, APIGroups: []string{"kubesphere.io", "account.kubesphere.io"}, ResourceNames: []string{workspace.Name}, Resources: []string{"workspaces", "workspaces/*"}, }, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces - - { - Verbs: []string{"create"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{workspace.Name}, - Resources: []string{"workspaces/namespaces"}, - }, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/members - - { - Verbs: []string{"create"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{workspace.Name}, - Resources: []string{"workspaces/members"}, - }, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/devops - { - Verbs: []string{"create"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{workspace.Name}, - Resources: []string{"workspaces/devops"}, - }, - // TODO have risks - // get apis/apps/v1/namespaces/proj1/deployments/?labelSelector - // post api/v1/namespaces/project-0vya57/limitranges { Verbs: []string{"*"}, - APIGroups: []string{"", "apps", "extensions", "batch"}, - Resources: []string{"limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumes", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"}, + APIGroups: []string{"devops.kubesphere.io", "jenkins.kubesphere.io"}, + Resources: []string{"*"}, }, - // get apis/kubesphere.io/v1alpha1/quota/namespaces/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"quota/*"}, - }, - // get api/v1/namespaces/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces", "serviceaccounts", "configmaps"}, - }, - // get api/v1/namespaces/proj1/serviceaccounts - // get api/v1/namespaces/proj1/configmaps - // get api/v1/namespaces/proj1/secrets - - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"serviceaccounts", "configmaps", "secrets"}, - }, - - // get apis/kubesphere.io/v1alpha1/status/namespaces/proj1 { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{"namespaces"}, - Resources: []string{"status/*"}, + Resources: []string{"status/*", "monitoring/*", "quota/*"}, }, - // apis/kubesphere.io/v1alpha1/namespaces/proj1/router - { - Verbs: []string{"list"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"router"}, - }, - // get apis/kubesphere.io/v1alpha1/registries/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"registries"}, - }, - - // get apis/kubesphere.io/v1alpha1/monitoring/namespaces/proj1 - - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{"namespaces"}, - Resources: []string{"monitoring/*"}, - }, - - // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims - // get apis/kubesphere.io/v1alpha1/resources/deployments - // get apis/kubesphere.io/v1alpha1/resources/statefulsets - // get apis/kubesphere.io/v1alpha1/resources/daemonsets - // get apis/kubesphere.io/v1alpha1/resources/jobs - // get apis/kubesphere.io/v1alpha1/resources/cronjobs - // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims - // get apis/kubesphere.io/v1alpha1/resources/services - // get apis/kubesphere.io/v1alpha1/resources/ingresses - // get apis/kubesphere.io/v1alpha1/resources/secrets - // get apis/kubesphere.io/v1alpha1/resources/configmaps - // get apis/kubesphere.io/v1alpha1/resources/roles - { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"resources"}, }, - - // apis/account.kubesphere.io/v1alpha1/users - // apis/account.kubesphere.io/v1alpha1/namespaces/proj1/users { Verbs: []string{"list"}, APIGroups: []string{"account.kubesphere.io"}, Resources: []string{"users"}, }, - - // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample?metrics_filter= - // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample/pods?step=30m { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, @@ -820,15 +898,42 @@ func WorkspaceRoleInit(workspace *Workspace) error { Resources: []string{"workspaces"}, ResourceNames: []string{workspace.Name}, }, { - Verbs: []string{"create", "get"}, + Verbs: []string{"create"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"workspaces/namespaces", "workspaces/devops"}, ResourceNames: []string{workspace.Name}, }, + { + Verbs: []string{"delete"}, + APIGroups: []string{"kubesphere.io"}, + Resources: []string{"workspaces/namespaces", "workspaces/devops"}, + ResourceNames: []string{workspace.Name}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{"namespaces"}, + Resources: []string{"quota/*", "status/*", "monitoring/*"}, + }, + { + Verbs: []string{"*"}, + APIGroups: []string{"devops.kubesphere.io"}, + Resources: []string{"*"}, + }, { + Verbs: []string{"*"}, + APIGroups: []string{"jenkins.kubesphere.io"}, + Resources: []string{"*"}, + }, { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, - Resources: []string{"registries"}, + Resources: []string{"resources"}, + }, + { + Verbs: []string{"get"}, + APIGroups: []string{"kubesphere.io"}, + ResourceNames: []string{workspace.Name}, + Resources: []string{"workspaces/members"}, }, } @@ -838,139 +943,38 @@ func WorkspaceRoleInit(workspace *Workspace) error { viewer.Name = fmt.Sprintf("system:%s:viewer", workspace.Name) viewer.Kind = iam.ClusterRoleKind viewer.Rules = []v1.PolicyRule{ - // apis/kubesphere.io/v1alpha1/workspaces/sample - // apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces - // apis/kubesphere.io/v1alpha1/workspaces/sample/devops - // apis/kubesphere.io/v1alpha1/workspaces/sample/roles - // apis/kubesphere.io/v1alpha1/workspaces/sample/members - // apis/kubesphere.io/v1alpha1/workspaces/sample/members/admin - { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io", "account.kubesphere.io"}, ResourceNames: []string{workspace.Name}, Resources: []string{"workspaces", "workspaces/*"}, }, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/namespaces - - //{ - // Verbs: []string{"create"}, - // APIGroups: []string{"kubesphere.io"}, - // ResourceNames: []string{workspace.Name}, - // Resources: []string{"workspaces/namespaces"}, - //}, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/members - - //{ - // Verbs: []string{"create"}, - // APIGroups: []string{"kubesphere.io"}, - // ResourceNames: []string{workspace.Name}, - // Resources: []string{"workspaces/members"}, - //}, - - // post apis/kubesphere.io/v1alpha1/workspaces/sample/devops - //{ - // Verbs: []string{"create"}, - // APIGroups: []string{"kubesphere.io"}, - // ResourceNames: []string{workspace.Name}, - // Resources: []string{"workspaces/devops"}, - //}, - // TODO have risks - // get apis/apps/v1/namespaces/proj1/deployments/?labelSelector - // post api/v1/namespaces/project-0vya57/limitranges - { - Verbs: []string{"get", "list"}, - APIGroups: []string{"", "apps", "extensions", "batch"}, - Resources: []string{"limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumes", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"}, - }, - // get apis/kubesphere.io/v1alpha1/quota/namespaces/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"quota/*"}, - }, - // get api/v1/namespaces/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{""}, - Resources: []string{"namespaces", "serviceaccounts", "configmaps"}, - }, - // get api/v1/namespaces/proj1/serviceaccounts - // get api/v1/namespaces/proj1/configmaps - // get api/v1/namespaces/proj1/secrets - - { - Verbs: []string{"list"}, - APIGroups: []string{""}, - Resources: []string{"serviceaccounts", "configmaps", "secrets"}, - }, - - // get apis/kubesphere.io/v1alpha1/status/namespaces/proj1 { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{"namespaces"}, - Resources: []string{"status/*"}, + Resources: []string{"quota/*", "status/*", "monitoring/*"}, }, - // apis/kubesphere.io/v1alpha1/namespaces/proj1/router - { - Verbs: []string{"list"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"router"}, - }, - // get apis/kubesphere.io/v1alpha1/registries/proj1 - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - Resources: []string{"registries"}, - }, - - // get apis/kubesphere.io/v1alpha1/monitoring/namespaces/proj1 - - { - Verbs: []string{"get"}, - APIGroups: []string{"kubesphere.io"}, - ResourceNames: []string{"namespaces"}, - Resources: []string{"monitoring/*"}, - }, - - // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims - // get apis/kubesphere.io/v1alpha1/resources/deployments - // get apis/kubesphere.io/v1alpha1/resources/statefulsets - // get apis/kubesphere.io/v1alpha1/resources/daemonsets - // get apis/kubesphere.io/v1alpha1/resources/jobs - // get apis/kubesphere.io/v1alpha1/resources/cronjobs - // get apis/kubesphere.io/v1alpha1/resources/persistent-volume-claims - // get apis/kubesphere.io/v1alpha1/resources/services - // get apis/kubesphere.io/v1alpha1/resources/ingresses - // get apis/kubesphere.io/v1alpha1/resources/secrets - // get apis/kubesphere.io/v1alpha1/resources/configmaps - // get apis/kubesphere.io/v1alpha1/resources/roles - { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, Resources: []string{"resources"}, }, - - // apis/account.kubesphere.io/v1alpha1/users - // apis/account.kubesphere.io/v1alpha1/namespaces/proj1/users - { - Verbs: []string{"list"}, - APIGroups: []string{"account.kubesphere.io"}, - Resources: []string{"users"}, - }, - - // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample?metrics_filter= - // apis/kubesphere.io/v1alpha1/monitoring/workspaces/sample/pods?step=30m { Verbs: []string{"get"}, APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{"workspaces"}, Resources: []string{"monitoring/" + workspace.Name}, }, + { + Verbs: []string{"get", "list"}, + APIGroups: []string{"devops.kubesphere.io"}, + Resources: []string{"*"}, + }, { + Verbs: []string{"get", "list"}, + APIGroups: []string{"jenkins.kubesphere.io"}, + Resources: []string{"*"}, + }, } viewer.Labels = map[string]string{"creator": "system"} @@ -1141,8 +1145,8 @@ func CreateWorkspaceRoleBinding(workspace *Workspace, username string, role stri } else { modify = true roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...) - if err != nil { - return err + if roleName == "admin" || roleName == "viewer" { + go deleteDevopsRoleBinding(workspace.Name, "", username) } break } @@ -1152,6 +1156,11 @@ func CreateWorkspaceRoleBinding(workspace *Workspace, username string, role stri if roleName == role { modify = true roleBinding.Subjects = append(roleBinding.Subjects, v1.Subject{Kind: v1.UserKind, Name: username}) + if roleName == "admin" { + go createDevopsRoleBinding(workspace.Name, "", username, "maintainer") + } else if roleName == "viewer" { + go createDevopsRoleBinding(workspace.Name, "", username, "reporter") + } } if !modify { @@ -1167,14 +1176,14 @@ func CreateWorkspaceRoleBinding(workspace *Workspace, username string, role stri return nil } -func GetDevOpsProjects(name string) ([]string, error) { +func GetDevOpsProjects(workspaceName string) ([]string, error) { db := client.NewSharedDBClient() defer db.Close() var workspaceDOPBindings []WorkspaceDPBinding - if err := db.Where("workspace = ?", name).Find(&workspaceDOPBindings).Error; err != nil { + if err := db.Where("workspace = ?", workspaceName).Find(&workspaceDOPBindings).Error; err != nil { return nil, err } @@ -1221,6 +1230,7 @@ func CountAll() (int, error) { return 0, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { return 0, err @@ -1270,7 +1280,7 @@ func GetAllDevOpsProjectsNums() (int, error) { defer db.Close() var count int - if err := db.Find(&WorkspaceDPBinding{}).Count(&count).Error; err != nil { + if err := db.Model(&WorkspaceDPBinding{}).Count(&count).Error; err != nil { return 0, err } return count, nil @@ -1283,6 +1293,7 @@ func GetAllAccountNums() (int, error) { return 0, err } + defer result.Body.Close() data, err := ioutil.ReadAll(result.Body) if err != nil { return 0, err