refactor tenant api

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2019-04-10 15:45:14 +08:00
committed by zryfish
parent 7163373064
commit 5c4efd53f6
29 changed files with 578 additions and 585 deletions

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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"},
},
},
},

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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, &params.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, &params.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, &params.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, &params.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)

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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/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/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"
"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/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"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)
func CreateDevopsProject(username string, workspace string, devops *models.DevopsProject) (*models.DevopsProject, error) {
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)
created, err := kubesphere.Client().CreateDevopsProject(username, 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)
err = BindingDevopsProject(workspace, created.ProjectId)
if err != nil {
return nil, err
}
defer result.Body.Close()
data, err = ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, kserr.Parse(data)
}
var project models.DevopsProject
err = json.Unmarshal(data, &project)
if err != nil {
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 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 {