From 5c4efd53f6b3a5caf0f6671569c6cb2684522579 Mon Sep 17 00:00:00 2001 From: hongming Date: Wed, 10 Apr 2019 15:45:14 +0800 Subject: [PATCH] refactor tenant api Signed-off-by: hongming --- pkg/apis/iam/v1alpha2/register.go | 3 +- pkg/apis/resources/v1alpha2/register.go | 19 +- pkg/apis/tenant/v1alpha2/register.go | 6 +- pkg/apis/terminal/v1alpha2/register.go | 2 +- pkg/apiserver/iam/groups.go | 2 +- pkg/apiserver/iam/im.go | 11 - pkg/apiserver/iam/workspaces.go | 12 +- pkg/apiserver/resources/application.go | 44 ++- pkg/apiserver/resources/resources.go | 9 +- pkg/apiserver/routers/routers.go | 7 +- pkg/apiserver/tenant/tenant.go | 48 ++- pkg/constants/constants.go | 2 - .../workspace/workspace_controller.go | 8 +- pkg/models/iam/am.go | 71 +--- pkg/models/iam/im.go | 148 ++++++-- pkg/models/iam/policy/policy.go | 16 +- pkg/models/resources/clusterroles.go | 14 + pkg/models/resources/resources.go | 21 +- pkg/models/routers/routers.go | 3 +- pkg/models/tenant/devops.go | 65 +--- pkg/models/tenant/namespaces.go | 9 + pkg/models/tenant/tenant.go | 60 ++- pkg/models/tenant/workspaces.go | 17 + pkg/models/types.go | 2 +- pkg/models/workspaces/workspaces.go | 350 +----------------- pkg/params/params.go | 12 - pkg/simple/client/kubesphere/devops.go | 186 ++++++++++ .../client/kubesphere/kubesphereclient.go | 7 + pkg/simple/client/openpitrix/applications.go | 5 +- 29 files changed, 576 insertions(+), 583 deletions(-) create mode 100644 pkg/simple/client/kubesphere/devops.go diff --git a/pkg/apis/iam/v1alpha2/register.go b/pkg/apis/iam/v1alpha2/register.go index 4613a6c5..d2e8e940 100644 --- a/pkg/apis/iam/v1alpha2/register.go +++ b/pkg/apis/iam/v1alpha2/register.go @@ -192,9 +192,10 @@ func addWebService(c *restful.Container) error { Param(ws.PathParameter("workspace", "workspace name")). Doc("Add user to workspace"). Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.POST("/workspaces/{workspace}/members"). + ws.Route(ws.DELETE("/workspaces/{workspace}/members/{username}"). To(iam.RemoveUser). Param(ws.PathParameter("workspace", "workspace name")). + Param(ws.PathParameter("name", "username")). Doc("Remove user from workspace"). Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/members/{username}"). diff --git a/pkg/apis/resources/v1alpha2/register.go b/pkg/apis/resources/v1alpha2/register.go index 71828b1b..a9c51d31 100644 --- a/pkg/apis/resources/v1alpha2/register.go +++ b/pkg/apis/resources/v1alpha2/register.go @@ -91,7 +91,7 @@ func addWebService(c *restful.Container) error { To(resources.ApplicationHandler). Writes(models.PageableResponse{}). Metadata(restfulspec.KeyOpenAPITags, tags). - Doc("Cluster level resource query"). + Doc("List applications in cluster"). Param(webservice.QueryParameter(params.ConditionsParam, "query conditions"). Required(false). DataFormat("key=value,key~value"). @@ -103,9 +103,24 @@ func addWebService(c *restful.Container) error { DataFormat("limit=%d,page=%d"). DefaultValue("limit=10,page=1"))) + webservice.Route(webservice.GET("/namespaces/{namespace}/applications"). + To(resources.NamespacedApplicationHandler). + Writes(models.PageableResponse{}). + Metadata(restfulspec.KeyOpenAPITags, tags). + Doc("List applications"). + Param(webservice.QueryParameter(params.ConditionsParam, "query conditions"). + Required(false). + DataFormat("key=value,key~value"). + DefaultValue("")). + Param(webservice.PathParameter("namespace", "namespace")). + Param(webservice.QueryParameter(params.PagingParam, "page"). + Required(false). + DataFormat("limit=%d,page=%d"). + DefaultValue("limit=10,page=1"))) + webservice.Route(webservice.GET("/storageclasses/{storageclass}/persistentvolumeclaims"). To(resources.GetPvcListBySc). - Doc("get user's kubectl pod"). + Doc("query persistent volume claims by storageclass"). Param(webservice.PathParameter("username", "username")). Metadata(restfulspec.KeyOpenAPITags, tags)) diff --git a/pkg/apis/tenant/v1alpha2/register.go b/pkg/apis/tenant/v1alpha2/register.go index cedfbf69..4c3048b2 100644 --- a/pkg/apis/tenant/v1alpha2/register.go +++ b/pkg/apis/tenant/v1alpha2/register.go @@ -42,6 +42,10 @@ func addWebService(c *restful.Container) error { To(tenant.ListWorkspaces). Doc("List workspace by user"). Metadata(restfulspec.KeyOpenAPITags, tags)) + ws.Route(ws.GET("/workspaces/{workspace}"). + To(tenant.DescribeWorkspace). + Doc("Get workspace detail"). + Metadata(restfulspec.KeyOpenAPITags, tags)) ws.Route(ws.GET("/workspaces/{workspace}/rules"). To(tenant.ListWorkspaceRules). Param(ws.PathParameter("workspace", "workspace name")). @@ -96,7 +100,7 @@ func addWebService(c *restful.Container) error { Param(ws.PathParameter("workspace", "workspace name")). Doc("Create devops project"). Metadata(restfulspec.KeyOpenAPITags, tags)) - ws.Route(ws.DELETE("/workspaces/{workspace}/devops"). + ws.Route(ws.DELETE("/workspaces/{workspace}/devops/{id}"). To(tenant.DeleteDevopsProject). Param(ws.PathParameter("workspace", "workspace name")). Doc("Delete devops project"). diff --git a/pkg/apis/terminal/v1alpha2/register.go b/pkg/apis/terminal/v1alpha2/register.go index 02af415c..258c6f7c 100644 --- a/pkg/apis/terminal/v1alpha2/register.go +++ b/pkg/apis/terminal/v1alpha2/register.go @@ -41,7 +41,7 @@ func addWebService(c *restful.Container) error { tags := []string{"Terminal"} - webservice.Route(webservice.GET("/namespace/{namespace}/pods/{pods}"). + webservice.Route(webservice.GET("/namespaces/{namespace}/pods/{pods}"). To(terminal.CreateTerminalSession). Doc("create terminal session"). Metadata(restfulspec.KeyOpenAPITags, tags). diff --git a/pkg/apiserver/iam/groups.go b/pkg/apiserver/iam/groups.go index 64daf66c..cd0cce07 100644 --- a/pkg/apiserver/iam/groups.go +++ b/pkg/apiserver/iam/groups.go @@ -142,7 +142,7 @@ func ListGroupUsers(req *restful.Request, resp *restful.Response) { for i := 0; i < len(group.Members); i++ { name := group.Members[i] - user, err := iam.DescribeUser(name) + user, err := iam.GetUserInfo(name) if err != nil { if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { diff --git a/pkg/apiserver/iam/im.go b/pkg/apiserver/iam/im.go index de248b7d..8caacea3 100644 --- a/pkg/apiserver/iam/im.go +++ b/pkg/apiserver/iam/im.go @@ -234,22 +234,11 @@ func ListUsers(req *restful.Request, resp *restful.Response) { conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) orderBy := req.QueryParameter(params.OrderByParam) reverse := params.ParseReverse(req) - names := params.ParseArray(req.QueryParameter(params.NameParam)) if err != nil { resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) return } - if len(names) > 0 { - users, err := iam.ListUsersByName(names) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - resp.WriteAsJson(users) - return - } users, err := iam.ListUsers(conditions, orderBy, reverse, limit, offset) diff --git a/pkg/apiserver/iam/workspaces.go b/pkg/apiserver/iam/workspaces.go index df045dac..67794b2d 100644 --- a/pkg/apiserver/iam/workspaces.go +++ b/pkg/apiserver/iam/workspaces.go @@ -91,7 +91,7 @@ func DescribeWorkspaceUser(req *restful.Request, resp *restful.Response) { return } - user, err := iam.DescribeUser(username) + user, err := iam.GetUserInfo(username) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) @@ -132,15 +132,9 @@ func InviteUser(req *restful.Request, resp *restful.Response) { func RemoveUser(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("workspace") - var user models.User - err := req.ReadEntity(&user) - if err != nil { - resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) - return - } - - err = workspaces.InviteUser(workspace, &user) + username := req.PathParameter("username") + err := workspaces.RemoveUser(workspace, username) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) return diff --git a/pkg/apiserver/resources/application.go b/pkg/apiserver/resources/application.go index 207d4760..1d17384e 100644 --- a/pkg/apiserver/resources/application.go +++ b/pkg/apiserver/resources/application.go @@ -19,10 +19,11 @@ package resources import ( "github.com/emicklei/go-restful" + "k8s.io/api/core/v1" + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/errors" "kubesphere.io/kubesphere/pkg/models/applications" - - //"kubesphere.io/kubesphere/pkg/models/applications" + "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/params" "net/http" ) @@ -58,3 +59,42 @@ func ApplicationHandler(req *restful.Request, resp *restful.Response) { resp.WriteAsJson(result) } + +func NamespacedApplicationHandler(req *restful.Request, resp *restful.Response) { + limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) + namespaceName := req.PathParameter("namespace") + conditions, err := params.ParseConditions(req.QueryParameter(params.ConditionsParam)) + if err != nil { + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) + return + } + } + + namespace, err := resources.GetResource("", resources.Namespaces, namespaceName) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + return + } + + var runtimeId string + + if ns, ok := namespace.(*v1.Namespace); ok { + runtimeId = ns.Annotations[constants.OpenPitrixRuntimeAnnotationKey] + } + + if runtimeId == "" { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.New("openpitrix runtime not init")) + return + } + + result, err := applications.ListApplication(runtimeId, conditions, limit, offset) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + return + } + + resp.WriteAsJson(result) +} diff --git a/pkg/apiserver/resources/resources.go b/pkg/apiserver/resources/resources.go index 6acaaddd..56b195fb 100644 --- a/pkg/apiserver/resources/resources.go +++ b/pkg/apiserver/resources/resources.go @@ -19,7 +19,6 @@ package resources import ( "github.com/emicklei/go-restful" - "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/models/resources" "net/http" @@ -34,7 +33,6 @@ func ListResources(req *restful.Request, resp *restful.Response) { orderBy := req.QueryParameter(params.OrderByParam) limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) reverse := params.ParseReverse(req) - names := params.ParseArray(req.QueryParameter(params.NameParam)) if orderBy == "" { orderBy = resources.CreateTime @@ -46,12 +44,7 @@ func ListResources(req *restful.Request, resp *restful.Response) { return } - var result *models.PageableResponse - if len(names) > 0 { - result, err = resources.ListResourcesByName(namespace, resourceName, names) - } else { - result, err = resources.ListResources(namespace, resourceName, conditions, orderBy, reverse, limit, offset) - } + result, err := resources.ListResources(namespace, resourceName, conditions, orderBy, reverse, limit, offset) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) diff --git a/pkg/apiserver/routers/routers.go b/pkg/apiserver/routers/routers.go index 083e41ef..471e2a7f 100644 --- a/pkg/apiserver/routers/routers.go +++ b/pkg/apiserver/routers/routers.go @@ -21,6 +21,7 @@ package routers import ( "fmt" "github.com/emicklei/go-restful" + k8serr "k8s.io/apimachinery/pkg/api/errors" "net/http" "kubesphere.io/kubesphere/pkg/errors" @@ -57,7 +58,11 @@ func GetRouter(request *restful.Request, response *restful.Response) { router, err := routers.GetRouter(namespace) if err != nil { - response.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + if k8serr.IsNotFound(err) { + response.WriteHeaderAndEntity(http.StatusNotFound, errors.Wrap(err)) + } else { + response.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + } return } diff --git a/pkg/apiserver/tenant/tenant.go b/pkg/apiserver/tenant/tenant.go index d22a75f7..294ab0d9 100644 --- a/pkg/apiserver/tenant/tenant.go +++ b/pkg/apiserver/tenant/tenant.go @@ -19,6 +19,7 @@ package tenant import ( "github.com/emicklei/go-restful" + "github.com/golang/glog" "k8s.io/api/core/v1" k8serr "k8s.io/apimachinery/pkg/api/errors" "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" @@ -26,10 +27,11 @@ import ( "kubesphere.io/kubesphere/pkg/errors" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/models/iam" + "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/models/tenant" "kubesphere.io/kubesphere/pkg/models/workspaces" "kubesphere.io/kubesphere/pkg/params" - "log" + "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "net/http" ) @@ -54,6 +56,11 @@ func ListWorkspaces(req *restful.Request, resp *restful.Response) { limit, offset := params.ParsePaging(req.QueryParameter(params.PagingParam)) reverse := params.ParseReverse(req) + if orderBy == "" { + orderBy = resources.CreateTime + reverse = true + } + if err != nil { resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) return @@ -69,6 +76,20 @@ func ListWorkspaces(req *restful.Request, resp *restful.Response) { resp.WriteAsJson(result) } +func DescribeWorkspace(req *restful.Request, resp *restful.Response) { + username := req.HeaderParameter(constants.UserNameHeader) + workspaceName := req.PathParameter("workspace") + + result, err := tenant.DescribeWorkspace(username, workspaceName) + + if err != nil { + resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + return + } + + resp.WriteAsJson(result) +} + func ListNamespaces(req *restful.Request, resp *restful.Response) { workspace := req.PathParameter("workspace") username := req.PathParameter("username") @@ -88,7 +109,7 @@ func ListNamespaces(req *restful.Request, resp *restful.Response) { return } - conditions.Match["kubesphere.io/workspace"] = workspace + conditions.Match[constants.WorkspaceLabelKey] = workspace result, err := tenant.ListNamespaces(username, conditions, orderBy, reverse, limit, offset) @@ -188,18 +209,24 @@ func ListDevopsProjects(req *restful.Request, resp *restful.Response) { func DeleteDevopsProject(req *restful.Request, resp *restful.Response) { devops := req.PathParameter("id") - workspace := req.PathParameter("workspace") - force := req.QueryParameter("force") + workspaceName := req.PathParameter("workspace") username := req.HeaderParameter(constants.UserNameHeader) - err := workspaces.UnBindDevopsProject(workspace, devops) + _, err := tenant.GetWorkspace(workspaceName) - if err != nil && force != "true" { + if err != nil { + resp.WriteHeaderAndEntity(http.StatusBadRequest, errors.Wrap(err)) + return + } + + err = kubesphere.Client().DeleteDevopsProject(username, devops) + + if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) return } - err = workspaces.DeleteDevopsProject(username, devops) + err = workspaces.UnBindDevopsProject(workspaceName, devops) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) @@ -211,7 +238,7 @@ func DeleteDevopsProject(req *restful.Request, resp *restful.Response) { func CreateDevopsProject(req *restful.Request, resp *restful.Response) { - workspace := req.PathParameter("workspace") + workspaceName := req.PathParameter("workspace") username := req.HeaderParameter(constants.UserNameHeader) var devops models.DevopsProject @@ -223,8 +250,8 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) { return } - log.Println("create workspace", username, workspace, devops) - project, err := workspaces.CreateDevopsProject(username, workspace, devops) + glog.Infoln("create workspace", username, workspaceName, devops) + project, err := workspaces.CreateDevopsProject(username, workspaceName, &devops) if err != nil { resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) @@ -232,7 +259,6 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) { } resp.WriteAsJson(project) - } func ListNamespaceRules(req *restful.Request, resp *restful.Response) { diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 344ab5f8..3225c158 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -47,7 +47,5 @@ const ( var ( WorkSpaceRoles = []string{WorkspaceAdmin, WorkspaceRegular, WorkspaceViewer} - SystemWorkspace = "system-workspace" - DevopsAPIServer = "ks-devops.kubesphere-devops-system.svc" SystemNamespaces = []string{KubeSphereNamespace, OpenPitrixNamespace, KubeSystemNamespace} ) diff --git a/pkg/controller/workspace/workspace_controller.go b/pkg/controller/workspace/workspace_controller.go index 32926b49..126b6a3e 100644 --- a/pkg/controller/workspace/workspace_controller.go +++ b/pkg/controller/workspace/workspace_controller.go @@ -153,9 +153,9 @@ func (r *ReconcileWorkspace) Reconcile(request reconcile.Request) (reconcile.Res return reconcile.Result{}, err } - if err = r.createGroup(instance); err != nil { - return reconcile.Result{}, err - } + //if err = r.createGroup(instance); err != nil { + // return reconcile.Result{}, err + //} if err = r.createWorkspaceRoleBindings(instance); err != nil { return reconcile.Result{}, err @@ -369,7 +369,7 @@ func (r *ReconcileWorkspace) createWorkspaceRoleBindings(instance *tenantv1alpha regularRoleBinding := &rbac.ClusterRoleBinding{} regularRoleBinding.Name = getWorkspaceRegularRoleBindingName(instance.Name) regularRoleBinding.Labels = map[string]string{constants.WorkspaceLabelKey: instance.Name} - regularRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceViewerRoleName(instance.Name)} + regularRoleBinding.RoleRef = rbac.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: getWorkspaceRegularRoleName(instance.Name)} regularRoleBinding.Subjects = []rbac.Subject{} if err = controllerutil.SetControllerReference(instance, regularRoleBinding, r.scheme); err != nil { diff --git a/pkg/models/iam/am.go b/pkg/models/iam/am.go index 0cc970ec..34501b25 100644 --- a/pkg/models/iam/am.go +++ b/pkg/models/iam/am.go @@ -18,20 +18,17 @@ package iam import ( - "encoding/json" - "errors" "fmt" "github.com/golang/glog" - "io/ioutil" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/params" "kubesphere.io/kubesphere/pkg/simple/client/k8s" + "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/utils/k8sutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "net/http" "sort" "strings" @@ -47,7 +44,7 @@ import ( const ClusterRoleKind = "ClusterRole" func GetUserDevopsSimpleRules(username, projectId string) ([]models.SimpleRule, error) { - role, err := GetUserDevopsRole(projectId, username) + role, err := kubesphere.Client().GetUserDevopsRole(username, projectId) if err != nil { return nil, err @@ -100,53 +97,6 @@ func GetDevopsRoleSimpleRules(role string) []models.SimpleRule { return rules } -func GetUserDevopsRole(projectId string, username string) (string, error) { - - //Hard fix - if username == "admin" { - return "owner", nil - } - - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects/%s/members", constants.DevopsAPIServer, projectId), nil) - - if err != nil { - return "", err - } - req.Header.Set(constants.UserNameHeader, username) - resp, err := http.DefaultClient.Do(req) - - if err != nil { - return "", err - } - - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return "", err - } - - if resp.StatusCode > 200 { - return "", errors.New(string(data)) - } - - var result []map[string]string - - err = json.Unmarshal(data, &result) - - if err != nil { - return "", err - } - - for _, item := range result { - if item["username"] == username { - return item["role"], nil - } - } - - return "", nil -} - // Get user roles in namespace func GetUserRoles(namespace, username string) ([]*v1.Role, error) { clusterRoleLister := informers.SharedInformerFactory().Rbac().V1().ClusterRoles().Lister() @@ -267,13 +217,6 @@ func GetUserRules(namespace, username string) ([]v1.PolicyRule, error) { return rules, nil } -func isUserFacingClusterRole(role *v1.ClusterRole) bool { - if role.Labels[constants.CreatorLabelKey] != "" { - return true - } - return false -} - func GetWorkspaceRoleBindings(workspace string) ([]*v1.ClusterRoleBinding, error) { clusterRoleBindings, err := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything()) @@ -386,7 +329,7 @@ func ListClusterRoleUsers(clusterRoleName string, conditions *params.Conditions, for _, roleBinding := range roleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := DescribeUser(subject.Name) + user, err := GetUserInfo(subject.Name) if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { continue } @@ -436,7 +379,7 @@ func RoleUsers(namespace string, roleName string) ([]*models.User, error) { for _, roleBinding := range roleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := DescribeUser(subject.Name) + user, err := GetUserInfo(subject.Name) if err != nil { if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { @@ -491,10 +434,14 @@ func NamespaceUsers(namespaceName string) ([]*models.User, error) { users := make([]*models.User, 0) for _, roleBinding := range roleBindings { + // controlled by ks-controller-manager + if roleBinding.Name == "admin" || roleBinding.Name == "viewer" { + continue + } for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := DescribeUser(subject.Name) + user, err := GetUserInfo(subject.Name) if err != nil { if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { diff --git a/pkg/models/iam/im.go b/pkg/models/iam/im.go index 7be15bea..3a5aa41e 100644 --- a/pkg/models/iam/im.go +++ b/pkg/models/iam/im.go @@ -26,6 +26,7 @@ import ( "kubesphere.io/kubesphere/pkg/simple/client/k8s" "kubesphere.io/kubesphere/pkg/simple/client/redis" "kubesphere.io/kubesphere/pkg/utils/k8sutil" + "kubesphere.io/kubesphere/pkg/utils/sliceutil" "regexp" "sort" "strconv" @@ -232,7 +233,7 @@ func ListUsersByName(names []string) (*models.PageableResponse, error) { for _, name := range names { if !k8sutil.ContainsUser(users, name) { - user, err := DescribeUser(name) + user, err := GetUserInfo(name) if err != nil { if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { continue @@ -252,6 +253,30 @@ func ListUsersByName(names []string) (*models.PageableResponse, error) { return &models.PageableResponse{Items: items, TotalCount: len(items)}, nil } +func ListUserByEmail(email []string) (*models.PageableResponse, error) { + users := make([]*models.User, 0) + for _, mail := range email { + user, err := GetUserInfoByEmail(mail) + if err != nil { + if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { + continue + } + return nil, err + } + if !k8sutil.ContainsUser(users, user.Username) { + users = append(users, user) + } + } + + items := make([]interface{}, 0) + + for _, u := range users { + items = append(items, u) + } + + return &models.PageableResponse{Items: items, TotalCount: len(items)}, nil +} + func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { conn, err := ldapclient.Client() @@ -272,6 +297,22 @@ func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limi filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword) } + if username := conditions.Match["username"]; username != "" { + uidFilter := "" + for _, username := range strings.Split(username, "|") { + uidFilter += fmt.Sprintf("(uid=%s)", username) + } + filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", uidFilter) + } + + if email := conditions.Match["email"]; email != "" { + emailFilter := "" + for _, username := range strings.Split(email, "|") { + emailFilter += fmt.Sprintf("(mail=%s)", username) + } + filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|%s))", emailFilter) + } + for { userSearchRequest := ldap.NewSearchRequest( ldapclient.UserSearchBase, @@ -331,26 +372,13 @@ func ListUsers(conditions *params.Conditions, orderBy string, reverse bool, limi if i >= offset && len(items) < limit { - avatar, err := getAvatar(user.Username) - if err != nil { - return nil, err - } - user.AvatarUrl = avatar - - lastLoginTime, err := getLastLoginTime(user.Username) - if err != nil { - return nil, err - } - user.LastLoginTime = lastLoginTime - + user.AvatarUrl = getAvatar(user.Username) + user.LastLoginTime = getLastLoginTime(user.Username) clusterRole, err := GetUserClusterRole(user.Username) - if err != nil { return nil, err } - user.ClusterRole = clusterRole.Name - items = append(items, user) } } @@ -373,23 +401,47 @@ func DescribeUser(username string) (*models.User, error) { } user.Groups = groups + user.AvatarUrl = getAvatar(username) - avatar, err := getAvatar(username) + return user, nil +} + +func GetUserInfoByEmail(mail string) (*models.User, error) { + conn, err := ldapclient.Client() if err != nil { return nil, err } - user.AvatarUrl = avatar + userSearchRequest := ldap.NewSearchRequest( + ldapclient.UserSearchBase, + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + fmt.Sprintf("(&(objectClass=inetOrgPerson)(mail=%s))", mail), + []string{"uid", "description", "preferredLanguage", "createTimestamp"}, + nil, + ) - lastLoginTime, err := getLastLoginTime(username) + result, err := conn.Search(userSearchRequest) if err != nil { return nil, err } - user.LastLoginTime = lastLoginTime + if len(result.Entries) != 1 { + return nil, ldap.NewError(ldap.LDAPResultNoSuchObject, fmt.Errorf("user %s does not exist", mail)) + } + username := result.Entries[0].GetAttributeValue("uid") + description := result.Entries[0].GetAttributeValue("description") + lang := result.Entries[0].GetAttributeValue("preferredLanguage") + createTimestamp, _ := time.Parse("20060102150405Z", result.Entries[0].GetAttributeValue("createTimestamp")) + user := &models.User{Username: username, Email: mail, Description: description, Lang: lang, CreateTime: createTimestamp} + user.LastLoginTime = getLastLoginTime(username) + clusterRole, err := GetUserClusterRole(user.Username) + if err != nil { + return nil, err + } + user.ClusterRole = clusterRole.Name return user, nil } @@ -426,6 +478,8 @@ func GetUserInfo(username string) (*models.User, error) { createTimestamp, _ := time.Parse("20060102150405Z", result.Entries[0].GetAttributeValue("createTimestamp")) user := &models.User{Username: username, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp} + user.LastLoginTime = getLastLoginTime(username) + return user, nil } @@ -462,17 +516,18 @@ func GetUserGroups(username string) ([]string, error) { return groups, nil } -func getLastLoginTime(username string) (string, error) { +func getLastLoginTime(username string) string { lastLogin, err := redis.Client().LRange(fmt.Sprintf("kubesphere:users:%s:login-log", username), -1, -1).Result() if err != nil { - return "", err + return "" } if len(lastLogin) > 0 { - return strings.Split(lastLogin[0], ",")[0], nil + return strings.Split(lastLogin[0], ",")[0] } - return "", nil + + return "" } func setAvatar(username, avatar string) error { @@ -480,20 +535,21 @@ func setAvatar(username, avatar string) error { return err } -func getAvatar(username string) (string, error) { +func getAvatar(username string) string { avatar, err := redis.Client().HMGet("kubesphere:users:avatar", username).Result() if err != nil { - return "", err + return "" } if len(avatar) > 0 { if url, ok := avatar[0].(string); ok { - return url, nil + return url } } - return "", nil + + return "" } func DeleteUser(username string) error { @@ -811,7 +867,7 @@ func UpdateUser(user *models.User) (*models.User, error) { return nil, err } - return DescribeUser(user.Username) + return GetUserInfo(user.Username) } func DeleteGroup(path string) error { @@ -1105,13 +1161,15 @@ func ListWorkspaceUsers(workspace string, conditions *params.Conditions, orderBy for _, roleBinding := range workspaceRoleBindings { for _, subject := range roleBinding.Subjects { if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) { - user, err := DescribeUser(subject.Name) + user, err := GetUserInfo(subject.Name) if err != nil { return nil, err } prefix := fmt.Sprintf("workspace:%s:", workspace) user.WorkspaceRole = fmt.Sprintf("workspace-%s", strings.TrimPrefix(roleBinding.Name, prefix)) - users = append(users, user) + if matchConditions(conditions, user) { + users = append(users, user) + } } } } @@ -1141,3 +1199,31 @@ func ListWorkspaceUsers(workspace string, conditions *params.Conditions, orderBy return &models.PageableResponse{Items: result, TotalCount: len(users)}, nil } + +func matchConditions(conditions *params.Conditions, user *models.User) bool { + for k, v := range conditions.Match { + switch k { + case "keyword": + if !strings.Contains(user.Username, v) && + !strings.Contains(user.Email, v) && + !strings.Contains(user.Description, v) { + return false + } + case "name": + names := strings.Split(v, "|") + if !sliceutil.HasString(names, user.Username) { + return false + } + case "email": + email := strings.Split(v, "|") + if !sliceutil.HasString(email, user.Email) { + return false + } + case "role": + if user.WorkspaceRole != v { + return false + } + } + } + return true +} diff --git a/pkg/models/iam/policy/policy.go b/pkg/models/iam/policy/policy.go index 3af78fd8..0843ac94 100644 --- a/pkg/models/iam/policy/policy.go +++ b/pkg/models/iam/policy/policy.go @@ -126,6 +126,18 @@ var ( }, }, }, + { + Name: "logging", + Actions: []models.Action{ + {Name: "view", + Rules: []v1.PolicyRule{{ + Verbs: []string{"get", "list"}, + APIGroups: []string{"logging.kubesphere.io"}, + Resources: []string{"*"}, + }}, + }, + }, + }, { Name: "accounts", Actions: []models.Action{ @@ -683,8 +695,8 @@ var ( Rules: []v1.PolicyRule{ { Verbs: []string{"get"}, - APIGroups: []string{"resources.kubesphere.io"}, - Resources: []string{"pod/terminal"}, + APIGroups: []string{"terminal.kubesphere.io"}, + Resources: []string{"pods"}, }, }, }, diff --git a/pkg/models/resources/clusterroles.go b/pkg/models/resources/clusterroles.go index 0c7b70d5..0de2e0ad 100644 --- a/pkg/models/resources/clusterroles.go +++ b/pkg/models/resources/clusterroles.go @@ -18,6 +18,7 @@ package resources import ( + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/params" "kubesphere.io/kubesphere/pkg/utils/k8sutil" @@ -55,6 +56,12 @@ func (*clusterRoleSearcher) match(match map[string]string, item *rbac.ClusterRol if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) { return false } + case "userfacing": + if v == "true" { + if !isUserFacingClusterRole(item) { + return false + } + } default: if item.Labels[k] != v { return false @@ -134,3 +141,10 @@ func (s *clusterRoleSearcher) search(namespace string, conditions *params.Condit } return r, nil } + +func isUserFacingClusterRole(role *rbac.ClusterRole) bool { + if role.Labels[constants.CreatorLabelKey] != "" && role.Labels[constants.WorkspaceLabelKey] == "" { + return true + } + return false +} diff --git a/pkg/models/resources/resources.go b/pkg/models/resources/resources.go index 59575220..ea415872 100644 --- a/pkg/models/resources/resources.go +++ b/pkg/models/resources/resources.go @@ -103,24 +103,15 @@ type resourceSearchInterface interface { search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) } -func ListResourcesByName(namespace, resource string, names []string) (*models.PageableResponse, error) { - items := make([]interface{}, 0) +func GetResource(namespace, resource, name string) (interface{}, error) { if searcher, ok := resources[resource]; ok { - for _, name := range names { - item, err := searcher.get(namespace, name) - - if err != nil { - return nil, err - } - - items = append(items, item) + resource, err := searcher.get(namespace, name) + if err != nil { + return nil, err } - - } else { - return nil, fmt.Errorf("not found") + return resource, nil } - - return &models.PageableResponse{TotalCount: len(items), Items: items}, nil + return nil, fmt.Errorf("resource %s not found", resource) } func ListResources(namespace, resource string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { diff --git a/pkg/models/routers/routers.go b/pkg/models/routers/routers.go index abcc0b71..ed9ec982 100644 --- a/pkg/models/routers/routers.go +++ b/pkg/models/routers/routers.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/golang/glog" "io/ioutil" + "k8s.io/apimachinery/pkg/api/errors" "kubesphere.io/kubesphere/pkg/simple/client/k8s" "sort" @@ -127,7 +128,7 @@ func GetRouter(namespace string) (*corev1.Service, error) { } } - return nil, fmt.Errorf("resources not found %s", serviceName) + return nil, errors.NewNotFound(corev1.Resource("service"), serviceName) } // Load all resource yamls diff --git a/pkg/models/tenant/devops.go b/pkg/models/tenant/devops.go index 5c68dc56..68fad574 100644 --- a/pkg/models/tenant/devops.go +++ b/pkg/models/tenant/devops.go @@ -18,15 +18,10 @@ package tenant import ( - "encoding/json" - "fmt" - "io/ioutil" - "kubesphere.io/kubesphere/pkg/constants" - kserr "kubesphere.io/kubesphere/pkg/errors" "kubesphere.io/kubesphere/pkg/models" "kubesphere.io/kubesphere/pkg/params" + "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/mysql" - "net/http" "sort" "strings" ) @@ -41,79 +36,55 @@ func ListDevopsProjects(workspace, username string, conditions *params.Condition return nil, err } - devOpsProjects := make([]models.DevopsProject, 0) - - request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects", constants.DevopsAPIServer), nil) - request.Header.Add(constants.UserNameHeader, username) - - resp, err := http.DefaultClient.Do(request) - if err != nil { - return nil, err - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err - } - - if resp.StatusCode > 200 { - return nil, kserr.Parse(data) - } - - err = json.Unmarshal(data, &devOpsProjects) - + projects, err := kubesphere.Client().ListDevopsProjects(username) if err != nil { return nil, err } if keyword := conditions.Match["keyword"]; keyword != "" { - for i := 0; i < len(devOpsProjects); i++ { - if !strings.Contains(devOpsProjects[i].Name, keyword) { - devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...) + for i := 0; i < len(projects); i++ { + if !strings.Contains(projects[i].Name, keyword) { + projects = append(projects[:i], projects[i+1:]...) i-- } } } - sort.Slice(devOpsProjects, func(i, j int) bool { + sort.Slice(projects, func(i, j int) bool { + if reverse { + tmp := i + i = j + j = tmp + } switch orderBy { case "name": - if reverse { - return devOpsProjects[i].Name < devOpsProjects[j].Name - } else { - return devOpsProjects[i].Name > devOpsProjects[j].Name - } + return projects[i].Name > projects[j].Name default: - if reverse { - return devOpsProjects[i].CreateTime.After(*devOpsProjects[j].CreateTime) - } else { - return devOpsProjects[i].CreateTime.Before(*devOpsProjects[j].CreateTime) - } + return projects[i].CreateTime.Before(*projects[j].CreateTime) } }) - for i := 0; i < len(devOpsProjects); i++ { + for i := 0; i < len(projects); i++ { inWorkspace := false for _, binding := range workspaceDOPBindings { - if binding.DevOpsProject == *devOpsProjects[i].ProjectId { + if binding.DevOpsProject == projects[i].ProjectId { inWorkspace = true } } if !inWorkspace { - devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...) + projects = append(projects[:i], projects[i+1:]...) i-- } } // limit offset result := make([]interface{}, 0) - for i, v := range devOpsProjects { + for i, v := range projects { if len(result) < limit && i >= offset { result = append(result, v) } } - return &models.PageableResponse{Items: result, TotalCount: len(devOpsProjects)}, nil + return &models.PageableResponse{Items: result, TotalCount: len(projects)}, nil } diff --git a/pkg/models/tenant/namespaces.go b/pkg/models/tenant/namespaces.go index 77758a77..72dc916f 100644 --- a/pkg/models/tenant/namespaces.go +++ b/pkg/models/tenant/namespaces.go @@ -21,6 +21,7 @@ import ( "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/labels" + "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/params" @@ -35,6 +36,14 @@ type namespaceSearcher struct { func (*namespaceSearcher) match(match map[string]string, item *v1.Namespace) bool { for k, v := range match { switch k { + case "name": + if item.Name != v && item.Labels[constants.DisplayNameLabelKey] != v { + return false + } + case "keyword": + if !strings.Contains(item.Name, v) && !contains(item.Labels, "", v) && !contains(item.Annotations, "", v) { + return false + } default: if item.Labels[k] != v { return false diff --git a/pkg/models/tenant/tenant.go b/pkg/models/tenant/tenant.go index e7bd311c..1f478b3a 100644 --- a/pkg/models/tenant/tenant.go +++ b/pkg/models/tenant/tenant.go @@ -19,7 +19,9 @@ package tenant import ( "k8s.io/api/core/v1" + "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models" ws "kubesphere.io/kubesphere/pkg/models/workspaces" "kubesphere.io/kubesphere/pkg/params" @@ -45,6 +47,18 @@ func CreateNamespace(workspaceName string, namespace *v1.Namespace, username str return k8s.Client().CoreV1().Namespaces().Create(namespace) } +func DescribeWorkspace(username, workspaceName string) (*v1alpha1.Workspace, error) { + workspace, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName) + + if err != nil { + return nil, err + } + + workspace = appendAnnotations(username, workspace) + + return workspace, nil +} + func ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { workspaces, err := workspaces.search(username, conditions, orderBy, reverse) @@ -57,25 +71,7 @@ func ListWorkspaces(username string, conditions *params.Conditions, orderBy stri result := make([]interface{}, 0) for i, workspace := range workspaces { if len(result) < limit && i >= offset { - workspace := workspace.DeepCopy() - ns, err := ListNamespaces(username, ¶ms.Conditions{Match: map[string]string{"kubesphere.io/workspace": workspace.Name}}, "", false, 1, 0) - if err != nil { - return nil, err - } - if workspace.Annotations == nil { - workspace.Annotations = make(map[string]string) - } - workspace.Annotations["kubesphere.io/namespace-count"] = strconv.Itoa(ns.TotalCount) - devops, err := ListDevopsProjects(workspace.Name, username, ¶ms.Conditions{}, "", false, 1, 0) - if err != nil { - return nil, err - } - workspace.Annotations["kubesphere.io/devops-count"] = strconv.Itoa(devops.TotalCount) - userCount, err := ws.WorkspaceUserCount(workspace.Name) - if err != nil { - return nil, err - } - workspace.Annotations["kubesphere.io/member-count"] = strconv.Itoa(userCount) + workspace := appendAnnotations(username, workspace) result = append(result, workspace) } } @@ -83,6 +79,32 @@ func ListWorkspaces(username string, conditions *params.Conditions, orderBy stri return &models.PageableResponse{Items: result, TotalCount: len(workspaces)}, nil } +func appendAnnotations(username string, workspace *v1alpha1.Workspace) *v1alpha1.Workspace { + workspace = workspace.DeepCopy() + if workspace.Annotations == nil { + workspace.Annotations = make(map[string]string) + } + ns, err := ListNamespaces(username, ¶ms.Conditions{Match: map[string]string{constants.WorkspaceLabelKey: workspace.Name}}, "", false, 1, 0) + if err == nil { + workspace.Annotations["kubesphere.io/namespace-count"] = strconv.Itoa(ns.TotalCount) + } else { + workspace.Annotations["kubesphere.io/namespace-count"] = "-1" + } + devops, err := ListDevopsProjects(workspace.Name, username, ¶ms.Conditions{}, "", false, 1, 0) + if err == nil { + workspace.Annotations["kubesphere.io/devops-count"] = strconv.Itoa(devops.TotalCount) + } else { + workspace.Annotations["kubesphere.io/devops-count"] = "-1" + } + userCount, err := ws.WorkspaceUserCount(workspace.Name) + if err == nil { + workspace.Annotations["kubesphere.io/member-count"] = strconv.Itoa(userCount) + } else { + workspace.Annotations["kubesphere.io/member-count"] = "-1" + } + return workspace +} + func ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) { namespaces, err := namespaces.search(username, conditions, orderBy, reverse) diff --git a/pkg/models/tenant/workspaces.go b/pkg/models/tenant/workspaces.go index 1ed84781..50d4860d 100644 --- a/pkg/models/tenant/workspaces.go +++ b/pkg/models/tenant/workspaces.go @@ -40,6 +40,10 @@ func (*workspaceSearcher) match(match map[string]string, item *v1alpha1.Workspac if item.Name != v && item.Labels[constants.DisplayNameLabelKey] != v { return false } + case "keyword": + if !strings.Contains(item.Name, v) && !contains(item.Labels, "", v) && !contains(item.Annotations, "", v) { + return false + } default: if item.Labels[k] != v { return false @@ -128,3 +132,16 @@ func (s *workspaceSearcher) search(username string, conditions *params.Condition func GetWorkspace(workspaceName string) (*v1alpha1.Workspace, error) { return informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName) } + +func contains(m map[string]string, key, value string) bool { + for k, v := range m { + if key == "" { + if strings.Contains(k, value) || strings.Contains(v, value) { + return true + } + } else if k == key && strings.Contains(v, value) { + return true + } + } + return false +} diff --git a/pkg/models/types.go b/pkg/models/types.go index e5989759..fd5a49cd 100644 --- a/pkg/models/types.go +++ b/pkg/models/types.go @@ -42,7 +42,7 @@ type WorkspaceDPBinding struct { } type DevopsProject struct { - ProjectId *string `json:"project_id,omitempty"` + ProjectId string `json:"project_id,omitempty"` Name string `json:"name"` Description string `json:"description"` Creator string `json:"creator"` diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go index 66e060b3..7a7ef15f 100644 --- a/pkg/models/workspaces/workspaces.go +++ b/pkg/models/workspaces/workspaces.go @@ -18,41 +18,29 @@ package workspaces import ( - "bytes" - "encoding/json" "fmt" - "io/ioutil" "k8s.io/apimachinery/pkg/runtime/schema" - "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/informers" + "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/params" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - "kubesphere.io/kubesphere/pkg/simple/client/ldap" + "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/simple/client/mysql" "kubesphere.io/kubesphere/pkg/utils/k8sutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "net/http" - - "kubesphere.io/kubesphere/pkg/constants" - "kubesphere.io/kubesphere/pkg/informers" - "kubesphere.io/kubesphere/pkg/models" - "kubesphere.io/kubesphere/pkg/models/iam" "strings" - "github.com/jinzhu/gorm" core "k8s.io/api/core/v1" "errors" - "github.com/golang/glog" "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - - "sort" - - kserr "kubesphere.io/kubesphere/pkg/errors" ) func UnBindDevopsProject(workspace string, devops string) error { @@ -60,191 +48,26 @@ func UnBindDevopsProject(workspace string, devops string) error { return db.Delete(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error } -func DeleteDevopsProject(username string, devops string) error { - request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, devops), nil) - request.Header.Add("X-Token-Username", username) - - result, err := http.DefaultClient.Do(request) - - if err != nil { - return err - } - defer result.Body.Close() - data, err := ioutil.ReadAll(result.Body) - if err != nil { - return err - } - if result.StatusCode > 200 { - return kserr.Parse(data) - } - return nil -} - -func CreateDevopsProject(username string, workspace string, devops models.DevopsProject) (*models.DevopsProject, error) { - - data, err := json.Marshal(devops) - - if err != nil { - return nil, err - } - - request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/api/v1alpha/projects", constants.DevopsAPIServer), bytes.NewReader(data)) - request.Header.Add("X-Token-Username", username) - request.Header.Add("Content-Type", "application/json") - result, err := http.DefaultClient.Do(request) - - if err != nil { - return nil, err - } - - defer result.Body.Close() - data, err = ioutil.ReadAll(result.Body) - - if err != nil { - return nil, err - } +func CreateDevopsProject(username string, workspace string, devops *models.DevopsProject) (*models.DevopsProject, error) { - if result.StatusCode > 200 { - return nil, kserr.Parse(data) - } - - var project models.DevopsProject - - err = json.Unmarshal(data, &project) + created, err := kubesphere.Client().CreateDevopsProject(username, devops) if err != nil { return nil, err } - err = BindingDevopsProject(workspace, *project.ProjectId) + err = BindingDevopsProject(workspace, created.ProjectId) if err != nil { - DeleteDevopsProject(username, *project.ProjectId) return nil, err } - go createDefaultDevopsRoleBinding(workspace, project) - - return &project, nil -} - -func createDefaultDevopsRoleBinding(workspace string, project models.DevopsProject) error { - admins := []string{""} - - for _, admin := range admins { - createDevopsRoleBinding(workspace, *project.ProjectId, admin, constants.DevopsOwner) - } - - viewers := []string{""} - - for _, viewer := range viewers { - createDevopsRoleBinding(workspace, *project.ProjectId, viewer, constants.DevopsReporter) - } - - return nil -} - -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)) - } - if resp != nil { - resp.Body.Close() - } - } -} - -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 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) - } - } - }) - - rules, err := iam.GetUserClusterRules(username) - - if err != nil { - return 0, nil, err - } - - namespacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{workspaceName}, Verbs: []string{"get"}, Resources: []string{"workspaces/namespaces"}} - - if !iam.RulesMatchesRequired(rules, namespacesManager) { - for i := 0; i < len(namespaces); i++ { - roles, err := iam.GetUserRoles(namespaces[i].Name, username) - if err != nil { - return 0, nil, err - } - rules := make([]v1.PolicyRule, 0) - for _, role := range roles { - rules = append(rules, role.Rules...) - } - if !iam.RulesMatchesRequired(rules, v1.PolicyRule{APIGroups: []string{""}, ResourceNames: []string{namespaces[i].Name}, Verbs: []string{"get"}, Resources: []string{"namespaces"}}) { - namespaces = append(namespaces[:i], namespaces[i+1:]...) - i-- - } - } - } - - 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 - } + return created, nil } func Namespaces(workspaceName string) ([]*core.Namespace, error) { namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister() - namespaces, err := namespaceLister.List(labels.SelectorFromSet(labels.Set{"kubesphere.io/workspace": workspaceName})) + namespaces, err := namespaceLister.List(labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: workspaceName})) if err != nil { return nil, err @@ -281,161 +104,18 @@ func DeleteNamespace(workspace string, namespaceName string) error { } } -func Delete(workspace *models.Workspace) error { - - err := release(workspace) - +func RemoveUser(workspaceName string, username string) error { + workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, username) if err != nil { return err } - - err = iam.DeleteGroup(workspace.Name) - + err = DeleteWorkspaceRoleBinding(workspaceName, username, workspaceRole.Labels[constants.DisplayNameLabelKey]) if err != nil { return err } - return nil } -// TODO -func release(workspace *models.Workspace) error { - for _, namespace := range workspace.Namespaces { - err := DeleteNamespace(workspace.Name, namespace) - if err != nil && !apierrors.IsNotFound(err) { - return err - } - } - - for _, devops := range workspace.DevopsProjects { - err := DeleteDevopsProject("admin", devops) - if err != nil && !strings.Contains(err.Error(), "not found") { - return err - } - } - - err := workspaceRoleRelease(workspace.Name) - - return err -} -func workspaceRoleRelease(workspace string) error { - k8sClient := k8s.Client() - deletePolicy := meta_v1.DeletePropagationForeground - - for _, role := range constants.WorkSpaceRoles { - err := k8sClient.RbacV1().ClusterRoles().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) - - if err != nil && !apierrors.IsNotFound(err) { - return err - } - } - - for _, role := range constants.WorkSpaceRoles { - err := k8sClient.RbacV1().ClusterRoleBindings().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy}) - - if err != nil && !apierrors.IsNotFound(err) { - return err - } - } - - return nil -} - -func Edit(workspace *models.Workspace) (*models.Workspace, error) { - - group, err := iam.UpdateGroup(&workspace.Group) - - if err != nil { - return nil, err - } - - workspace.Group = *group - - return workspace, nil -} - -func DescribeWorkspace(workspaceName string) (*v1alpha1.Workspace, error) { - workspace, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName) - - if err != nil { - return nil, err - } - - return workspace, nil -} - -func fetch(names []string) ([]*models.Workspace, error) { - - if names != nil && len(names) == 0 { - return make([]*models.Workspace, 0), nil - } - var groups []models.Group - var err error - if names == nil { - groups, err = iam.ChildList("") - if err != nil { - return nil, err - } - } else { - conn, err := ldap.Client() - if err != nil { - return nil, err - } - defer conn.Close() - for _, name := range names { - group, err := iam.DescribeGroup(name) - if err != nil { - return nil, err - } - groups = append(groups, *group) - } - } - - db := mysql.Client() - - workspaces := make([]*models.Workspace, 0) - for _, group := range groups { - workspace, err := convertGroupToWorkspace(db, group) - if err != nil { - return nil, err - } - workspaces = append(workspaces, workspace) - } - - return workspaces, nil -} - -func convertGroupToWorkspace(db *gorm.DB, group models.Group) (*models.Workspace, error) { - namespaces, err := Namespaces(group.Name) - - if err != nil { - return nil, err - } - - namespacesNames := make([]string, 0) - - for _, namespace := range namespaces { - namespacesNames = append(namespacesNames, namespace.Name) - } - - var workspaceDOPBindings []models.WorkspaceDPBinding - - if err := db.Where("workspace = ?", group.Name).Find(&workspaceDOPBindings).Error; err != nil { - return nil, err - } - - devOpsProjects := make([]string, 0) - - for _, workspaceDOPBinding := range workspaceDOPBindings { - devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject) - } - - workspace := models.Workspace{Group: group} - workspace.Namespaces = namespacesNames - workspace.DevopsProjects = devOpsProjects - return &workspace, nil -} - func InviteUser(workspaceName string, user *models.User) error { workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, user.Username) @@ -463,7 +143,6 @@ func CreateWorkspaceRoleBinding(workspace, username string, role string) error { } roleBindingName := fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-")) - workspaceRoleBinding, err := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister().Get(roleBindingName) workspaceRoleBinding = workspaceRoleBinding.DeepCopy() if err != nil { @@ -487,11 +166,10 @@ func DeleteWorkspaceRoleBinding(workspace, username string, role string) error { roleBindingName := fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-")) workspaceRoleBinding, err := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister().Get(roleBindingName) - workspaceRoleBinding = workspaceRoleBinding.DeepCopy() - if err != nil { return err } + workspaceRoleBinding = workspaceRoleBinding.DeepCopy() for i, v := range workspaceRoleBinding.Subjects { if v.Kind == v1.UserKind && v.Name == username { diff --git a/pkg/params/params.go b/pkg/params/params.go index dc679be2..3e14c3f1 100644 --- a/pkg/params/params.go +++ b/pkg/params/params.go @@ -82,18 +82,6 @@ func ParseReverse(req *restful.Request) bool { return b } -func ParseArray(str string) []string { - arr := make([]string, 0) - - for _, item := range strings.Split(str, ",") { - if item = strings.TrimSpace(item); item != "" { - arr = append(arr, item) - } - } - - return arr -} - type Conditions struct { Match map[string]string Fuzzy map[string]string diff --git a/pkg/simple/client/kubesphere/devops.go b/pkg/simple/client/kubesphere/devops.go new file mode 100644 index 00000000..c67cb3ae --- /dev/null +++ b/pkg/simple/client/kubesphere/devops.go @@ -0,0 +1,186 @@ +/* + + 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 kubesphere + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/golang/glog" + "io/ioutil" + "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/models" + "net/http" +) + +func (c client) DeleteDevopsProject(username string, projectId string) error { + request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1alpha/projects/%s", devopsAPIServer, projectId), nil) + if username == "" { + username = constants.AdminUserName + } + request.Header.Add("X-Token-Username", username) + + resp, err := c.client.Do(request) + + if err != nil { + return err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if resp.StatusCode > http.StatusOK { + return Error{resp.StatusCode, string(data)} + } + return nil +} + +func (c client) GetUserDevopsRole(username string, projectId string) (string, error) { + + if username == "admin" { + return "owner", nil + } + + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1alpha/projects/%s/members", devopsAPIServer, projectId), nil) + + if err != nil { + return "", err + } + req.Header.Set(constants.UserNameHeader, username) + resp, err := c.client.Do(req) + + if err != nil { + return "", err + } + + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return "", err + } + + if resp.StatusCode > http.StatusOK { + return "", Error{resp.StatusCode, string(data)} + } + + var result []map[string]string + + err = json.Unmarshal(data, &result) + + if err != nil { + return "", err + } + + for _, item := range result { + if item["username"] == username { + return item["role"], nil + } + } + + return "", nil +} + +func (c client) CreateDevopsProject(username string, project *models.DevopsProject) (*models.DevopsProject, error) { + data, err := json.Marshal(project) + + if err != nil { + return nil, err + } + + request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1alpha/projects", devopsAPIServer), bytes.NewReader(data)) + request.Header.Add("X-Token-Username", username) + request.Header.Add("Content-Type", "application/json") + resp, err := c.client.Do(request) + + if err != nil { + return nil, err + } + + defer resp.Body.Close() + data, err = ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, err + } + + if resp.StatusCode > http.StatusOK { + return nil, Error{resp.StatusCode, string(data)} + } + + var created models.DevopsProject + + err = json.Unmarshal(data, &created) + + if err != nil { + return nil, err + } + + return &created, nil +} + +func (c client) CreateDevopsRoleBinding(projectId string, user string, role string) { + + projects := make([]string, 0) + projects = append(projects, projectId) + + for _, project := range projects { + data := []byte(fmt.Sprintf(`{"username":"%s","role":"%s"}`, user, role)) + request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1alpha/projects/%s/members", devopsAPIServer, project), bytes.NewReader(data)) + request.Header.Add("Content-Type", "application/json") + request.Header.Add("X-Token-Username", "admin") + resp, err := c.client.Do(request) + if err != nil || resp.StatusCode > 200 { + glog.Warning(fmt.Sprintf("create devops role binding failed %s,%s,%s", project, user, role)) + } + if resp != nil { + resp.Body.Close() + } + } +} + +func (c client) ListDevopsProjects(username string) ([]models.DevopsProject, error) { + projects := make([]models.DevopsProject, 0) + + request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1alpha/projects", devopsAPIServer), nil) + request.Header.Add(constants.UserNameHeader, username) + + resp, err := c.client.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, err + } + + if resp.StatusCode > http.StatusOK { + return nil, Error{resp.StatusCode, string(data)} + } + + err = json.Unmarshal(data, &projects) + + if err != nil { + return nil, err + } + + return projects, nil +} diff --git a/pkg/simple/client/kubesphere/kubesphereclient.go b/pkg/simple/client/kubesphere/kubesphereclient.go index 88ae0f38..6c72e976 100644 --- a/pkg/simple/client/kubesphere/kubesphereclient.go +++ b/pkg/simple/client/kubesphere/kubesphereclient.go @@ -32,6 +32,7 @@ import ( var ( accountAPIServer string + devopsAPIServer string once sync.Once c client ) @@ -41,6 +42,11 @@ type Interface interface { UpdateGroup(group *models.Group) (*models.Group, error) DescribeGroup(name string) (*models.Group, error) DeleteGroup(name string) error + DeleteDevopsProject(username string, projectId string) error + GetUserDevopsRole(username string, projectId string) (string, error) + CreateDevopsProject(username string, project *models.DevopsProject) (*models.DevopsProject, error) + CreateDevopsRoleBinding(projectId string, user string, role string) + ListDevopsProjects(username string) ([]models.DevopsProject, error) } type client struct { @@ -49,6 +55,7 @@ type client struct { func init() { flag.StringVar(&accountAPIServer, "ks-account-api-server", "http://ks-account.kubesphere-system.svc", "kubesphere account api server") + flag.StringVar(&devopsAPIServer, "ks-devops-api-server", "http://ks-devops.kubesphere-devops-system.svc", "kubesphere devops api server") } func Client() Interface { diff --git a/pkg/simple/client/openpitrix/applications.go b/pkg/simple/client/openpitrix/applications.go index 9e95e62f..9c00f9c4 100644 --- a/pkg/simple/client/openpitrix/applications.go +++ b/pkg/simple/client/openpitrix/applications.go @@ -273,11 +273,10 @@ func makeHttpRequest(method, url, data string) ([]byte, error) { return nil, err } - httpClient := &http.Client{} - resp, err := httpClient.Do(req) + resp, err := http.DefaultClient.Do(req) if err != nil { - err := fmt.Errorf("Request to %s failed, method: %s, reason: %s ", url, method, err) + err := fmt.Errorf("Request to %s failed, method: %s,token: %s, reason: %s ", url, method, openpitrixProxyToken, err) glog.Error(err) return nil, err } -- GitLab