refine tenant api

Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2019-04-01 02:59:19 +08:00
parent 744bd053e3
commit 93ad572e19
202 changed files with 13517 additions and 7951 deletions

View File

@@ -18,11 +18,8 @@
package applications
import (
"encoding/json"
"fmt"
"github.com/golang/glog"
"io/ioutil"
v12 "k8s.io/api/apps/v1"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/api/errors"
@@ -33,24 +30,11 @@ import (
"kubesphere.io/kubesphere/pkg/models/resources"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"net/http"
"strconv"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"strings"
"time"
)
var (
OpenPitrixProxyToken string
OpenPitrixServer string
)
const (
unknown = "-"
deploySuffix = "-Deployment"
daemonSuffix = "-DaemonSet"
stateSuffix = "-StatefulSet"
)
type Application struct {
Name string `json:"name"`
RepoName string `json:"repoName"`
@@ -70,174 +54,93 @@ type Application struct {
ClusterID string `json:"cluster_id"`
}
type clusterRole struct {
ClusterID string `json:"cluster_id"`
Role string `json:"role"`
}
type cluster struct {
ClusterID string `json:"cluster_id"`
Name string `json:"name"`
AppID string `json:"app_id"`
VersionID string `json:"version_id"`
Status string `json:"status"`
UpdateTime time.Time `json:"status_time"`
CreateTime time.Time `json:"create_time"`
RunTimeId string `json:"runtime_id"`
Description string `json:"description"`
ClusterRoleSets []clusterRole `json:"cluster_role_set"`
}
type clusters struct {
Total int `json:"total_count"`
Clusters []cluster `json:"cluster_set"`
}
type versionList struct {
Total int `json:"total_count"`
Versions []version `json:"app_version_set"`
}
type version struct {
Name string `json:"name"`
VersionID string `json:"version_id"`
}
type runtime struct {
RuntimeID string `json:"runtime_id"`
Zone string `json:"zone"`
}
type runtimeList struct {
Total int `json:"total_count"`
Runtimes []runtime `json:"runtime_set"`
}
type app struct {
AppId string `json:"app_id"`
Name string `json:"name"`
ChartName string `json:"chart_name"`
RepoId string `json:"repo_id"`
}
type repo struct {
RepoId string `json:"repo_id"`
Name string `json:"name"`
Url string `json:"url"`
}
type workLoads struct {
Deployments []v12.Deployment `json:"deployments,omitempty"`
Statefulsets []v12.StatefulSet `json:"statefulsets,omitempty"`
Daemonsets []v12.DaemonSet `json:"daemonsets,omitempty"`
Deployments []appsv1.Deployment `json:"deployments,omitempty"`
Statefulsets []appsv1.StatefulSet `json:"statefulsets,omitempty"`
Daemonsets []appsv1.DaemonSet `json:"daemonsets,omitempty"`
}
type appList struct {
Total int `json:"total_count"`
Apps []app `json:"app_set"`
func ListApplication(runtimeId string, conditions *params.Conditions, limit, offset int) (*models.PageableResponse, error) {
clusterList, err := openpitrix.ListClusters(runtimeId, conditions.Match["keyword"], conditions.Match["status"], limit, offset)
if err != nil {
return nil, err
}
result := models.PageableResponse{TotalCount: clusterList.Total}
result.Items = make([]interface{}, 0)
for _, item := range clusterList.Clusters {
var app Application
app.Name = item.Name
app.ClusterID = item.ClusterID
app.UpdateTime = item.UpdateTime
app.Status = item.Status
versionInfo, _ := openpitrix.GetVersion(item.VersionID)
app.Version = versionInfo
app.VersionId = item.VersionID
runtimeInfo, _ := openpitrix.GetRuntime(item.RunTimeId)
app.Runtime = runtimeInfo
app.RuntimeId = item.RunTimeId
appInfo, _, appId, _ := openpitrix.GetAppInfo(item.AppID)
app.App = appInfo
app.AppId = appId
app.Description = item.Description
result.Items = append(result.Items, app)
}
return &result, nil
}
type repoList struct {
Total int `json:"total_count"`
Repos []repo `json:"repo_set"`
func GetApp(clusterId string) (*Application, error) {
item, err := openpitrix.GetCluster(clusterId)
if err != nil {
return nil, err
}
var app Application
app.Name = item.Name
app.ClusterID = item.ClusterID
app.UpdateTime = item.UpdateTime
app.CreateTime = item.CreateTime
app.Status = item.Status
versionInfo, _ := openpitrix.GetVersion(item.VersionID)
app.Version = versionInfo
app.VersionId = item.VersionID
runtimeInfo, _ := openpitrix.GetRuntime(item.RunTimeId)
app.Runtime = runtimeInfo
app.RuntimeId = item.RunTimeId
appInfo, repoId, appId, _ := openpitrix.GetAppInfo(item.AppID)
app.App = appInfo
app.AppId = appId
app.Description = item.Description
app.RepoName, _ = openpitrix.GetRepo(repoId)
workloads, err := getWorkLoads(app.Runtime, item.ClusterRoleSets)
if err != nil {
glog.Error(err)
return nil, err
}
app.WorkLoads = workloads
workloadLabels := getLabels(app.Runtime, app.WorkLoads)
app.Services = getSvcs(app.Runtime, workloadLabels)
app.Ingresses = getIng(app.Runtime, app.Services)
return &app, nil
}
func GetAppInfo(appId string) (string, string, string, error) {
url := fmt.Sprintf("%s/v1/apps?app_id=%s", OpenPitrixServer, appId)
resp, err := makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return unknown, unknown, unknown, err
}
var apps appList
err = json.Unmarshal(resp, &apps)
if err != nil {
glog.Error(err)
return unknown, unknown, unknown, err
}
if len(apps.Apps) == 0 {
return unknown, unknown, unknown, err
}
return apps.Apps[0].ChartName, apps.Apps[0].RepoId, apps.Apps[0].AppId, nil
}
func GetRepo(repoId string) (string, error) {
url := fmt.Sprintf("%s/v1/repos?repo_id=%s", OpenPitrixServer, repoId)
resp, err := makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return unknown, err
}
var repos repoList
err = json.Unmarshal(resp, &repos)
if err != nil {
glog.Error(err)
return unknown, err
}
if len(repos.Repos) == 0 {
return unknown, err
}
return repos.Repos[0].Name, nil
}
func GetVersion(versionId string) (string, error) {
versionUrl := fmt.Sprintf("%s/v1/app_versions?version_id=%s", OpenPitrixServer, versionId)
resp, err := makeHttpRequest("GET", versionUrl, "")
if err != nil {
glog.Error(err)
return unknown, err
}
var versions versionList
err = json.Unmarshal(resp, &versions)
if err != nil {
glog.Error(err)
return unknown, err
}
if len(versions.Versions) == 0 {
return unknown, nil
}
return versions.Versions[0].Name, nil
}
func GetRuntime(runtimeId string) (string, error) {
versionUrl := fmt.Sprintf("%s/v1/runtimes?runtime_id=%s", OpenPitrixServer, runtimeId)
resp, err := makeHttpRequest("GET", versionUrl, "")
if err != nil {
glog.Error(err)
return unknown, err
}
var runtimes runtimeList
err = json.Unmarshal(resp, &runtimes)
if err != nil {
glog.Error(err)
return unknown, err
}
if len(runtimes.Runtimes) == 0 {
return unknown, nil
}
return runtimes.Runtimes[0].Zone, nil
}
func GetWorkLoads(namespace string, clusterRoles []clusterRole) (*workLoads, error) {
func getWorkLoads(namespace string, clusterRoles []openpitrix.ClusterRole) (*workLoads, error) {
var works workLoads
for _, clusterRole := range clusterRoles {
workLoadName := clusterRole.Role
if len(workLoadName) > 0 {
if strings.HasSuffix(workLoadName, deploySuffix) {
name := strings.Split(workLoadName, deploySuffix)[0]
if strings.HasSuffix(workLoadName, openpitrix.DeploySuffix) {
name := strings.Split(workLoadName, openpitrix.DeploySuffix)[0]
item, err := informers.SharedInformerFactory().Apps().V1().Deployments().Lister().Deployments(namespace).Get(name)
@@ -249,8 +152,8 @@ func GetWorkLoads(namespace string, clusterRoles []clusterRole) (*workLoads, err
continue
}
if strings.HasSuffix(workLoadName, daemonSuffix) {
name := strings.Split(workLoadName, daemonSuffix)[0]
if strings.HasSuffix(workLoadName, openpitrix.DaemonSuffix) {
name := strings.Split(workLoadName, openpitrix.DaemonSuffix)[0]
item, err := informers.SharedInformerFactory().Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name)
if err != nil {
return nil, err
@@ -259,8 +162,8 @@ func GetWorkLoads(namespace string, clusterRoles []clusterRole) (*workLoads, err
continue
}
if strings.HasSuffix(workLoadName, stateSuffix) {
name := strings.Split(workLoadName, stateSuffix)[0]
if strings.HasSuffix(workLoadName, openpitrix.StateSuffix) {
name := strings.Split(workLoadName, openpitrix.StateSuffix)[0]
item, err := informers.SharedInformerFactory().Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name)
if err != nil {
return nil, err
@@ -346,7 +249,7 @@ func getIng(namespace string, services []v1.Service) []v1beta1.Ingress {
var ings []v1beta1.Ingress
for _, svc := range services {
result, err := resources.ListNamespaceResource(namespace, "ingress", &params.Conditions{Fuzzy: map[string]string{"serviceName": svc.Name}}, "", false, -1, 0)
result, err := resources.ListResources(namespace, "ingress", &params.Conditions{Fuzzy: map[string]string{"serviceName": svc.Name}}, "", false, -1, 0)
if err != nil {
glog.Error(err)
return nil
@@ -379,159 +282,3 @@ func getIng(namespace string, services []v1.Service) []v1beta1.Ingress {
return ings
}
func ListApplication(runtimeId string, conditions *params.Conditions, limit, offset int) (*models.PageableResponse, error) {
if strings.HasSuffix(OpenPitrixServer, "/") {
OpenPitrixServer = strings.TrimSuffix(OpenPitrixServer, "/")
}
defaultStatus := "status=active&status=stopped&status=pending&status=ceased"
url := fmt.Sprintf("%s/v1/clusters?limit=%s&offset=%s", OpenPitrixServer, strconv.Itoa(limit), strconv.Itoa(offset))
if len(conditions.Fuzzy["name"]) > 0 {
url = fmt.Sprintf("%s&search_word=%s", url, conditions.Fuzzy["name"])
}
if len(conditions.Match["status"]) > 0 {
url = fmt.Sprintf("%s&status=%s", url, conditions.Match["status"])
} else {
url = fmt.Sprintf("%s&%s", url, defaultStatus)
}
if len(runtimeId) > 0 {
url = fmt.Sprintf("%s&runtime_id=%s", url, runtimeId)
}
resp, err := makeHttpRequest("GET", url, "")
if err != nil {
glog.Errorf("request %s failed, reason: %s", url, err)
return nil, err
}
var clusterList clusters
err = json.Unmarshal(resp, &clusterList)
if err != nil {
return nil, err
}
result := models.PageableResponse{TotalCount: clusterList.Total}
result.Items = make([]interface{}, 0)
for _, item := range clusterList.Clusters {
var app Application
app.Name = item.Name
app.ClusterID = item.ClusterID
app.UpdateTime = item.UpdateTime
app.Status = item.Status
versionInfo, _ := GetVersion(item.VersionID)
app.Version = versionInfo
app.VersionId = item.VersionID
runtimeInfo, _ := GetRuntime(item.RunTimeId)
app.Runtime = runtimeInfo
app.RuntimeId = item.RunTimeId
appInfo, _, appId, _ := GetAppInfo(item.AppID)
app.App = appInfo
app.AppId = appId
app.Description = item.Description
result.Items = append(result.Items, app)
}
return &result, nil
}
func GetApp(clusterId string) (*Application, error) {
if strings.HasSuffix(OpenPitrixServer, "/") {
OpenPitrixServer = strings.TrimSuffix(OpenPitrixServer, "/")
}
url := fmt.Sprintf("%s/v1/clusters?cluster_id=%s", OpenPitrixServer, clusterId)
resp, err := makeHttpRequest("GET", url, "")
if err != nil {
glog.Error(err)
return nil, err
}
var clusterList clusters
err = json.Unmarshal(resp, &clusterList)
if err != nil {
glog.Error(err)
return nil, err
}
if len(clusterList.Clusters) == 0 {
return nil, fmt.Errorf("NotFound, clusterId:%s", clusterId)
}
item := clusterList.Clusters[0]
var app Application
app.Name = item.Name
app.ClusterID = item.ClusterID
app.UpdateTime = item.UpdateTime
app.CreateTime = item.CreateTime
app.Status = item.Status
versionInfo, _ := GetVersion(item.VersionID)
app.Version = versionInfo
app.VersionId = item.VersionID
runtimeInfo, _ := GetRuntime(item.RunTimeId)
app.Runtime = runtimeInfo
app.RuntimeId = item.RunTimeId
appInfo, repoId, appId, _ := GetAppInfo(item.AppID)
app.App = appInfo
app.AppId = appId
app.Description = item.Description
app.RepoName, _ = GetRepo(repoId)
workloads, err := GetWorkLoads(app.Runtime, item.ClusterRoleSets)
if err != nil {
glog.Error(err)
return nil, err
}
app.WorkLoads = workloads
workloadLabels := getLabels(app.Runtime, app.WorkLoads)
app.Services = getSvcs(app.Runtime, workloadLabels)
app.Ingresses = getIng(app.Runtime, app.Services)
return &app, nil
}
func makeHttpRequest(method, url, data string) ([]byte, error) {
var req *http.Request
var err error
if method == "GET" {
req, err = http.NewRequest(method, url, nil)
} else {
req, err = http.NewRequest(method, url, strings.NewReader(data))
}
req.Header.Add("Authorization", OpenPitrixProxyToken)
if err != nil {
glog.Error(err)
return nil, err
}
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
err := fmt.Errorf("Request to %s failed, method: %s, reason: %s ", url, method, err)
glog.Error(err)
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
err = fmt.Errorf(string(body))
}
return body, err
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +0,0 @@
/*
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 iam
import "sync"
type Counter struct {
value int
m *sync.Mutex
}
func NewCounter(value int) Counter {
c := Counter{}
c.m = &sync.Mutex{}
c.Set(value)
return c
}
func (c *Counter) Set(value int) {
c.m.Lock()
c.value = value
c.m.Unlock()
}
func (c *Counter) Add(value int) {
c.m.Lock()
c.value += value
c.m.Unlock()
}
func (c *Counter) Sub(value int) {
c.m.Lock()
c.value -= value
c.m.Unlock()
}
func (c *Counter) Get() int {
return c.value
}

View File

@@ -1,188 +0,0 @@
/*
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 iam
import (
"fmt"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
"k8s.io/api/rbac/v1"
"k8s.io/kubernetes/pkg/util/slice"
"kubesphere.io/kubesphere/pkg/models"
)
const ClusterRoleKind = "ClusterRole"
// Get user list based on workspace role
func WorkspaceRoleUsers(workspace string, roleName string) ([]models.User, error) {
clusterRoleBindingLister := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister()
workspaceRoleBinding, err := clusterRoleBindingLister.Get(fmt.Sprintf("system:%s:%s", workspace, roleName))
if err != nil {
return nil, err
}
names := make([]string, 0)
for _, subject := range workspaceRoleBinding.Subjects {
if subject.Kind == v1.UserKind {
names = append(names, subject.Name)
}
}
users, err := GetUsers(names)
if err != nil {
return nil, err
}
for i := 0; i < len(users); i++ {
users[i].WorkspaceRole = roleName
}
return users, nil
}
func GetUsers(names []string) ([]models.User, error) {
var users []models.User
if names == nil || len(names) == 0 {
return make([]models.User, 0), nil
}
conn, err := ldap.Client()
if err != nil {
return nil, err
}
for _, name := range names {
user, err := UserDetail(name, conn)
if err != nil {
return nil, err
}
users = append(users, *user)
}
return users, nil
}
func GetUser(name string) (*models.User, error) {
conn, err := ldap.Client()
if err != nil {
return nil, err
}
user, err := UserDetail(name, conn)
if err != nil {
return nil, err
}
return user, nil
}
func GetUserNamespaces(username string, requiredRule v1.PolicyRule) (allNamespace bool, namespaces []string, err error) {
clusterRoles, err := GetClusterRoles(username)
if err != nil {
return false, nil, err
}
clusterRules := make([]v1.PolicyRule, 0)
for _, role := range clusterRoles {
clusterRules = append(clusterRules, role.Rules...)
}
if requiredRule.Size() == 0 {
if RulesMatchesRequired(clusterRules, v1.PolicyRule{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces"},
}) {
return true, nil, nil
}
} else {
if RulesMatchesRequired(clusterRules, requiredRule) {
return true, nil, nil
}
}
roles, err := GetRoles("", username)
if err != nil {
return false, nil, err
}
rulesMapping := make(map[string][]v1.PolicyRule, 0)
for _, role := range roles {
rules := rulesMapping[role.Namespace]
if rules == nil {
rules = make([]v1.PolicyRule, 0)
}
rules = append(rules, role.Rules...)
rulesMapping[role.Namespace] = rules
}
namespaces = make([]string, 0)
for namespace, rules := range rulesMapping {
if requiredRule.Size() == 0 || RulesMatchesRequired(rules, requiredRule) {
namespaces = append(namespaces, namespace)
}
}
return false, namespaces, nil
}
func GetWorkspaceUsers(workspace string, workspaceRole string) ([]string, error) {
clusterRoleBindingLister := informers.SharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister()
clusterRoleBinding, err := clusterRoleBindingLister.Get(fmt.Sprintf("system:%s:%s", workspace, workspaceRole))
if err != nil {
return nil, err
}
users := make([]string, 0)
for _, s := range clusterRoleBinding.Subjects {
if s.Kind == v1.UserKind && !slice.ContainsString(users, s.Name, nil) {
users = append(users, s.Name)
}
}
return users, nil
}
func RulesMatchesRequired(rules []v1.PolicyRule, required v1.PolicyRule) bool {
for _, rule := range rules {
if ruleMatchesRequired(rule, required) {
return true
}
}
return false
}

View File

@@ -22,9 +22,12 @@ import (
"fmt"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/redis"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"regexp"
"sort"
"strconv"
"strings"
"time"
@@ -38,11 +41,10 @@ import (
ldapclient "kubesphere.io/kubesphere/pkg/simple/client/ldap"
"kubesphere.io/kubesphere/pkg/models"
jwtutils "kubesphere.io/kubesphere/pkg/utils/jwt"
"kubesphere.io/kubesphere/pkg/utils/jwtutil"
)
var (
counter Counter
adminEmail string
adminPassword string
tokenExpireTime time.Duration
@@ -82,7 +84,7 @@ func checkAndCreateDefaultGroup(conn ldap.Client) error {
nil,
)
groups, err := conn.Search(groupSearchRequest)
_, err := conn.Search(groupSearchRequest)
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
err = createGroupsBaseDN(conn)
@@ -95,14 +97,6 @@ func checkAndCreateDefaultGroup(conn ldap.Client) error {
return fmt.Errorf("iam database init failed: %s\n", err)
}
if groups == nil || len(groups.Entries) == 0 {
_, err = CreateGroup(models.Group{Path: constants.SystemWorkspace, Name: constants.SystemWorkspace, Creator: constants.AdminUserName, Description: "system workspace"})
if err != nil {
return fmt.Errorf("system-workspace create failed: %s\n", err)
}
}
return nil
}
@@ -130,13 +124,10 @@ func checkAndCreateDefaultUser(conn ldap.Client) error {
}
if users == nil || len(users.Entries) == 0 {
counter = NewCounter(0)
err := CreateUser(models.User{Username: constants.AdminUserName, Email: adminEmail, Password: adminPassword, Description: "Administrator account that was always created by default."})
_, err := CreateUser(&models.User{Username: constants.AdminUserName, Email: adminEmail, Password: adminPassword, Description: "Administrator account that was always created by default."})
if err != nil {
return fmt.Errorf("admin create failed: %s\n", err)
}
} else {
counter = NewCounter(len(users.Entries))
}
return nil
@@ -164,12 +155,12 @@ func createGroupsBaseDN(conn ldap.Client) error {
}
// User login
func Login(username string, password string, ip string) (string, error) {
func Login(username string, password string, ip string) (*models.Token, error) {
conn, err := ldapclient.Client()
if err != nil {
return "", err
return nil, err
}
defer conn.Close()
@@ -185,11 +176,11 @@ func Login(username string, password string, ip string) (string, error) {
result, err := conn.Search(userSearchRequest)
if err != nil {
return "", err
return nil, err
}
if len(result.Entries) != 1 {
return "", ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("incorrect password"))
return nil, ldap.NewError(ldap.LDAPResultInvalidCredentials, errors.New("incorrect password"))
}
uid := result.Entries[0].GetAttributeValue("uid")
@@ -200,7 +191,7 @@ func Login(username string, password string, ip string) (string, error) {
err = conn.Bind(dn, password)
if err != nil {
return "", err
return nil, err
}
claims := jwt.MapClaims{}
@@ -209,13 +200,11 @@ func Login(username string, password string, ip string) (string, error) {
claims["username"] = uid
claims["email"] = email
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
uToken, _ := token.SignedString(jwtutils.Secret)
token := jwtutil.MustSigned(claims)
loginLog(uid, ip)
return uToken, nil
return &models.Token{Token: token}, nil
}
func loginLog(uid, ip string) {
@@ -226,99 +215,6 @@ func loginLog(uid, ip string) {
}
}
func UserList(limit int, offset int) (int, []models.User, error) {
conn, err := ldapclient.Client()
if err != nil {
return 0, nil, err
}
defer conn.Close()
users := make([]models.User, 0)
pageControl := ldap.NewControlPaging(1000)
entries := make([]*ldap.Entry, 0)
cursor := 0
l1:
for {
userSearchRequest := ldap.NewSearchRequest(
ldapclient.UserSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(&(objectClass=inetOrgPerson))",
[]string{"uid", "mail", "description"},
[]ldap.Control{pageControl},
)
response, err := conn.Search(userSearchRequest)
if err != nil {
return 0, nil, err
}
for _, entry := range response.Entries {
cursor++
if cursor > offset {
if len(entries) < limit {
entries = append(entries, entry)
} else {
break l1
}
}
}
updatedControl := ldap.FindControl(response.Controls, ldap.ControlTypePaging)
if ctrl, ok := updatedControl.(*ldap.ControlPaging); ctrl != nil && ok && len(ctrl.Cookie) != 0 {
pageControl.SetCookie(ctrl.Cookie)
continue
}
break
}
redisClient := redis.Client()
for _, v := range entries {
uid := v.GetAttributeValue("uid")
email := v.GetAttributeValue("mail")
description := v.GetAttributeValue("description")
user := models.User{Username: uid, Email: email, Description: description}
avatar, err := redisClient.HMGet("kubesphere:users:avatar", uid).Result()
if err != nil {
return 0, nil, err
}
if len(avatar) > 0 {
if url, ok := avatar[0].(string); ok {
user.AvatarUrl = url
}
}
lastLogin, err := redisClient.LRange(fmt.Sprintf("kubesphere:users:%s:login-log", uid), -1, -1).Result()
if err != nil {
return 0, nil, err
}
if len(lastLogin) > 0 {
user.LastLoginTime = strings.Split(lastLogin[0], ",")[0]
}
user.ClusterRules = make([]models.SimpleRule, 0)
users = append(users, user)
}
return counter.Get(), users, nil
}
func LoginLog(username string) ([]string, error) {
redisClient := redis.Client()
@@ -331,48 +227,77 @@ func LoginLog(username string) ([]string, error) {
return data, nil
}
func Search(keyword string, limit int, offset int) (int, []models.User, error) {
func ListUsersByName(names []string) (*models.PageableResponse, error) {
users := make([]*models.User, 0)
for _, name := range names {
if !k8sutil.ContainsUser(users, name) {
user, err := DescribeUser(name)
if err != nil {
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
continue
}
return nil, err
}
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()
if err != nil {
return 0, nil, err
return nil, err
}
defer conn.Close()
users := make([]models.User, 0)
pageControl := ldap.NewControlPaging(80)
entries := make([]*ldap.Entry, 0)
users := make([]models.User, 0)
filter := "(&(objectClass=inetOrgPerson))"
if keyword := conditions.Match["keyword"]; keyword != "" {
filter = fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword)
}
cursor := 0
l1:
for {
userSearchRequest := ldap.NewSearchRequest(
ldapclient.UserSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=inetOrgPerson)(|(uid=*%s*)(mail=*%s*)(description=*%s*)))", keyword, keyword, keyword),
[]string{"uid", "mail", "description"},
filter,
[]string{"uid", "mail", "description", "preferredLanguage", "createTimestamp"},
[]ldap.Control{pageControl},
)
response, err := conn.Search(userSearchRequest)
if err != nil {
return 0, nil, err
return nil, err
}
for _, entry := range response.Entries {
cursor++
if cursor > offset {
if len(entries) < limit {
entries = append(entries, entry)
} else {
break l1
}
}
uid := entry.GetAttributeValue("uid")
email := entry.GetAttributeValue("mail")
description := entry.GetAttributeValue("description")
lang := entry.GetAttributeValue("preferredLanguage")
createTimestamp, _ := time.Parse("20060102150405Z", entry.GetAttributeValue("createTimestamp"))
user := models.User{Username: uid, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp}
users = append(users, user)
}
updatedControl := ldap.FindControl(response.Controls, ldap.ControlTypePaging)
@@ -384,52 +309,104 @@ l1:
break
}
redisClient := redis.Client()
for _, v := range entries {
uid := v.GetAttributeValue("uid")
email := v.GetAttributeValue("mail")
description := v.GetAttributeValue("description")
user := models.User{Username: uid, Email: email, Description: description}
avatar, err := redisClient.HMGet("kubesphere:users:avatar", uid).Result()
if err != nil {
return 0, nil, err
sort.Slice(users, func(i, j int) bool {
if reverse {
tmp := i
i = j
j = tmp
}
switch orderBy {
case "username":
fallthrough
case "createTime":
return users[i].CreateTime.Before(users[j].CreateTime)
default:
return strings.Compare(users[i].Username, users[j].Username) <= 0
}
})
if len(avatar) > 0 {
if url, ok := avatar[0].(string); ok {
user.AvatarUrl = url
items := make([]interface{}, 0)
for i, user := range users {
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
clusterRole, err := GetUserClusterRole(user.Username)
if err != nil {
return nil, err
}
user.ClusterRole = clusterRole.Name
items = append(items, user)
}
lastLogin, err := redisClient.LRange(fmt.Sprintf("kubesphere:users:%s:login-log", uid), -1, -1).Result()
if err != nil {
return 0, nil, err
}
if len(lastLogin) > 0 {
user.LastLoginTime = strings.Split(lastLogin[0], ",")[0]
}
user.ClusterRules = make([]models.SimpleRule, 0)
users = append(users, user)
}
return counter.Get(), users, nil
return &models.PageableResponse{Items: items, TotalCount: len(users)}, nil
}
func UserDetail(username string, conn ldap.Client) (*models.User, error) {
func DescribeUser(username string) (*models.User, error) {
user, err := GetUserInfo(username)
if err != nil {
return nil, err
}
groups, err := GetUserGroups(username)
if err != nil {
return nil, err
}
user.Groups = groups
avatar, err := getAvatar(username)
if err != nil {
return nil, err
}
user.AvatarUrl = avatar
lastLoginTime, err := getLastLoginTime(username)
if err != nil {
return nil, err
}
user.LastLoginTime = lastLoginTime
return user, nil
}
// Get user info only included email description & lang
func GetUserInfo(username string) (*models.User, error) {
conn, err := ldapclient.Client()
if err != nil {
return nil, err
}
userSearchRequest := ldap.NewSearchRequest(
ldapclient.UserSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=inetOrgPerson)(uid=%s))", username),
[]string{"mail", "description", "preferredLanguage"},
[]string{"mail", "description", "preferredLanguage", "createTimestamp"},
nil,
)
@@ -446,7 +423,20 @@ func UserDetail(username string, conn ldap.Client) (*models.User, error) {
email := result.Entries[0].GetAttributeValue("mail")
description := result.Entries[0].GetAttributeValue("description")
lang := result.Entries[0].GetAttributeValue("preferredLanguage")
user := models.User{Username: username, Email: email, Description: description, Lang: lang}
createTimestamp, _ := time.Parse("20060102150405Z", result.Entries[0].GetAttributeValue("createTimestamp"))
user := &models.User{Username: username, Email: email, Description: description, Lang: lang, CreateTime: createTimestamp}
return user, nil
}
func GetUserGroups(username string) ([]string, error) {
conn, err := ldapclient.Client()
if err != nil {
return nil, err
}
defer conn.Close()
groupSearchRequest := ldap.NewSearchRequest(
ldapclient.GroupSearchBase,
@@ -456,11 +446,10 @@ func UserDetail(username string, conn ldap.Client) (*models.User, error) {
nil,
)
result, err = conn.Search(groupSearchRequest)
result, err := conn.Search(groupSearchRequest)
if err != nil {
return nil, err
}
groups := make([]string, 0)
@@ -470,41 +459,47 @@ func UserDetail(username string, conn ldap.Client) (*models.User, error) {
groups = append(groups, groupName)
}
user.Groups = groups
return groups, nil
}
redisClient := redis.Client()
avatar, err := redisClient.HMGet("kubesphere:users:avatar", username).Result()
func getLastLoginTime(username string) (string, error) {
lastLogin, err := redis.Client().LRange(fmt.Sprintf("kubesphere:users:%s:login-log", username), -1, -1).Result()
if err != nil {
return nil, err
return "", err
}
if len(lastLogin) > 0 {
return strings.Split(lastLogin[0], ",")[0], nil
}
return "", nil
}
func setAvatar(username, avatar string) error {
_, err := redis.Client().HMSet("kubesphere:users:avatar", map[string]interface{}{"username": avatar}).Result()
return err
}
func getAvatar(username string) (string, error) {
avatar, err := redis.Client().HMGet("kubesphere:users:avatar", username).Result()
if err != nil {
return "", err
}
if len(avatar) > 0 {
if url, ok := avatar[0].(string); ok {
user.AvatarUrl = url
return url, nil
}
}
user.Status = 0
lastLogin, err := redisClient.LRange(fmt.Sprintf("kubesphere:users:%s:login-log", username), -1, -1).Result()
if err != nil {
return nil, err
}
if len(lastLogin) > 0 {
user.LastLoginTime = strings.Split(lastLogin[0], ",")[0]
}
return &user, nil
return "", nil
}
func DeleteUser(username string) error {
// bind root DN
conn, err := ldapclient.Client()
if err != nil {
return err
}
@@ -521,13 +516,7 @@ func DeleteUser(username string) error {
err = deleteRoleBindings(username)
if err != nil {
return err
}
counter.Sub(1)
return nil
return err
}
func deleteRoleBindings(username string) error {
@@ -539,7 +528,7 @@ func deleteRoleBindings(username string) error {
}
for _, roleBinding := range roleBindings {
roleBinding = roleBinding.DeepCopy()
length1 := len(roleBinding.Subjects)
for index, subject := range roleBinding.Subjects {
@@ -571,6 +560,7 @@ func deleteRoleBindings(username string) error {
clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything())
for _, clusterRoleBinding := range clusterRoleBindings {
clusterRoleBinding = clusterRoleBinding.DeepCopy()
length1 := len(clusterRoleBinding.Subjects)
for index, subject := range clusterRoleBinding.Subjects {
@@ -637,7 +627,7 @@ func UserCreateCheck(check string) (exist bool, err error) {
}
}
func CreateUser(user models.User) error {
func CreateUser(user *models.User) (*models.User, error) {
user.Username = strings.TrimSpace(user.Username)
user.Email = strings.TrimSpace(user.Email)
user.Password = strings.TrimSpace(user.Password)
@@ -646,7 +636,7 @@ func CreateUser(user models.User) error {
conn, err := ldapclient.Client()
if err != nil {
return err
return nil, err
}
defer conn.Close()
@@ -662,17 +652,17 @@ func CreateUser(user models.User) error {
result, err := conn.Search(userSearchRequest)
if err != nil {
return err
return nil, err
}
if len(result.Entries) > 0 {
return ldap.NewError(ldap.LDAPResultEntryAlreadyExists, fmt.Errorf("username or email already exists"))
return nil, ldap.NewError(ldap.LDAPResultEntryAlreadyExists, fmt.Errorf("username or email already exists"))
}
maxUid, err := getMaxUid(conn)
if err != nil {
return err
return nil, err
}
maxUid += 1
@@ -688,7 +678,7 @@ func CreateUser(user models.User) error {
userCreateRequest.Attribute("mail", []string{user.Email}) // RFC1274: RFC822 Mailbox
userCreateRequest.Attribute("userPassword", []string{user.Password}) // RFC4519/2307: password of user
if user.Lang != "" {
userCreateRequest.Attribute("preferredLanguage", []string{user.Lang}) // RFC4519/2307: password of user
userCreateRequest.Attribute("preferredLanguage", []string{user.Lang})
}
if user.Description != "" {
userCreateRequest.Attribute("description", []string{user.Description}) // RFC4519: descriptive information
@@ -697,16 +687,22 @@ func CreateUser(user models.User) error {
err = conn.Add(userCreateRequest)
if err != nil {
return err
return nil, err
}
counter.Add(1)
if user.AvatarUrl != "" {
setAvatar(user.Username, user.AvatarUrl)
}
if user.ClusterRole != "" {
CreateClusterRoleBinding(user.Username, user.ClusterRole)
err := CreateClusterRoleBinding(user.Username, user.ClusterRole)
if err != nil {
return nil, err
}
}
return nil
return DescribeUser(user.Username)
}
func getMaxUid(conn ldap.Client) (int, error) {
@@ -768,11 +764,12 @@ func getMaxGid(conn ldap.Client) (int, error) {
return maxGid, nil
}
func UpdateUser(user models.User) error {
func UpdateUser(user *models.User) (*models.User, error) {
conn, err := ldapclient.Client()
if err != nil {
return err
return nil, err
}
defer conn.Close()
@@ -794,19 +791,27 @@ func UpdateUser(user models.User) error {
userModifyRequest.Replace("userPassword", []string{user.Password})
}
if user.AvatarUrl != "" {
err = setAvatar(user.Username, user.AvatarUrl)
}
if err != nil {
return nil, err
}
err = conn.Modify(userModifyRequest)
if err != nil {
return err
return nil, err
}
err = CreateClusterRoleBinding(user.Username, user.ClusterRole)
if err != nil {
return err
return nil, err
}
return nil
return DescribeUser(user.Username)
}
func DeleteGroup(path string) error {
@@ -829,13 +834,14 @@ func DeleteGroup(path string) error {
return nil
}
func CreateGroup(group models.Group) (*models.Group, error) {
func CreateGroup(group *models.Group) (*models.Group, error) {
// bind root DN
conn, err := ldapclient.Client()
if err != nil {
return nil, err
}
defer conn.Close()
maxGid, err := getMaxGid(conn)
@@ -861,7 +867,9 @@ func CreateGroup(group models.Group) (*models.Group, error) {
groupCreateRequest.Attribute("description", []string{group.Description})
}
groupCreateRequest.Attribute("memberUid", []string{group.Creator})
if group.Members != nil {
groupCreateRequest.Attribute("memberUid", group.Members)
}
err = conn.Add(groupCreateRequest)
@@ -871,18 +879,7 @@ func CreateGroup(group models.Group) (*models.Group, error) {
group.Gid = strconv.Itoa(maxGid)
group.CreateTime = time.Now().UTC().Format("2006-01-02T15:04:05Z")
redisClient := redis.Client()
if err := redisClient.HMSet("kubesphere:groups:create-time", map[string]interface{}{group.Name: group.CreateTime}).Err(); err != nil {
return nil, err
}
if err := redisClient.HMSet("kubesphere:groups:creator", map[string]interface{}{group.Name: group.Creator}).Err(); err != nil {
return nil, err
}
return &group, nil
return DescribeGroup(group.Path)
}
func UpdateGroup(group *models.Group) (*models.Group, error) {
@@ -894,7 +891,7 @@ func UpdateGroup(group *models.Group) (*models.Group, error) {
}
defer conn.Close()
old, err := GroupDetail(group.Path, conn)
old, err := DescribeGroup(group.Path)
if err != nil {
return nil, err
@@ -1027,34 +1024,22 @@ func ChildList(path string) ([]models.Group, error) {
group.ChildGroups = childGroups
redisClient := redis.Client()
createTime, _ := redisClient.HMGet("kubesphere:groups:create-time", group.Name).Result()
if len(createTime) > 0 {
if t, ok := createTime[0].(string); ok {
group.CreateTime = t
}
}
creator, _ := redisClient.HMGet("kubesphere:groups:creator", group.Name).Result()
if len(creator) > 0 {
if t, ok := creator[0].(string); ok {
group.Creator = t
}
}
groups = append(groups, group)
}
return groups, nil
}
func GroupDetail(path string, conn ldap.Client) (*models.Group, error) {
func DescribeGroup(path string) (*models.Group, error) {
searchBase, cn := splitPath(path)
conn, err := ldapclient.Client()
if err != nil {
return nil, err
}
groupSearchRequest := ldap.NewSearchRequest(searchBase,
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=posixGroup)(cn=%s))", cn),
@@ -1083,24 +1068,76 @@ func GroupDetail(path string, conn ldap.Client) (*models.Group, error) {
group.ChildGroups = childGroups
redisClient := redis.Client()
createTime, _ := redisClient.HMGet("kubesphere:groups:create-time", group.Name).Result()
if len(createTime) > 0 {
if t, ok := createTime[0].(string); ok {
group.CreateTime = t
}
}
creator, _ := redisClient.HMGet("kubesphere:groups:creator", group.Name).Result()
if len(creator) > 0 {
if t, ok := creator[0].(string); ok {
group.Creator = t
}
}
return &group, nil
}
func WorkspaceUsersTotalCount(workspace string) (int, error) {
workspaceRoleBindings, err := GetWorkspaceRoleBindings(workspace)
if err != nil {
return 0, err
}
users := make([]string, 0)
for _, roleBinding := range workspaceRoleBindings {
for _, subject := range roleBinding.Subjects {
if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) {
users = append(users, subject.Name)
}
}
}
return len(users), nil
}
func ListWorkspaceUsers(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
workspaceRoleBindings, err := GetWorkspaceRoleBindings(workspace)
if err != nil {
return nil, err
}
users := make([]*models.User, 0)
for _, roleBinding := range workspaceRoleBindings {
for _, subject := range roleBinding.Subjects {
if subject.Kind == v1.UserKind && !k8sutil.ContainsUser(users, subject.Name) {
user, err := DescribeUser(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)
}
}
}
// order & reverse
sort.Slice(users, func(i, j int) bool {
if reverse {
tmp := i
i = j
j = tmp
}
switch orderBy {
default:
fallthrough
case "name":
return strings.Compare(users[i].Username, users[j].Username) <= 0
}
})
result := make([]interface{}, 0)
for i, d := range users {
if i >= offset && (limit == -1 || len(result) < limit) {
result = append(result, d)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(users)}, nil
}

View File

@@ -20,6 +20,7 @@ package policy
import (
"encoding/json"
"fmt"
"io/ioutil"
"kubesphere.io/kubesphere/pkg/models"
@@ -55,292 +56,25 @@ func init() {
}
var (
WorkspaceRoleRuleMapping = []models.Rule{
{
Name: "workspaces",
Actions: []models.Action{
{Name: "edit",
Rules: []v1.PolicyRule{
{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces"},
}, {
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/*"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"jenkins.kubesphere.io"},
Resources: []string{"*"},
}, {
Verbs: []string{"*"},
APIGroups: []string{"devops.kubesphere.io"},
Resources: []string{"*"},
},
},
},
{Name: "delete",
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces"},
},
},
},
},
},
{Name: "members",
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/members"},
},
},
},
{Name: "create",
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/members"},
},
},
},
{Name: "edit",
Rules: []v1.PolicyRule{
{
Verbs: []string{"patch", "update"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/members"},
},
},
},
{Name: "delete",
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/members"},
},
},
},
},
},
{
Name: "devops",
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/devops"},
},
},
},
{Name: "create",
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/devops"},
},
},
},
{Name: "edit",
Rules: []v1.PolicyRule{
{
Verbs: []string{"update", "patch"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/devops"},
},
},
},
{Name: "delete",
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/devops"},
},
},
},
},
},
{
Name: "projects",
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces"},
},
},
},
{Name: "create",
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces"},
},
},
},
{Name: "edit",
Rules: []v1.PolicyRule{
{
Verbs: []string{"update", "patch"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces"},
},
},
},
{Name: "delete",
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces"},
},
},
},
},
},
{
Name: "organizations",
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"workspaces/organizations"},
},
},
},
{Name: "create",
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"workspaces/organizations"},
},
},
},
{Name: "edit",
Rules: []v1.PolicyRule{
{
Verbs: []string{"update", "patch"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"workspaces/organizations"},
},
},
},
{Name: "delete",
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"workspaces/organizations"},
},
},
}},
},
{
Name: "roles",
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/roles"},
},
}},
},
},
}
ClusterRoleRuleMapping = []models.Rule{
{Name: "workspaces",
Actions: []models.Action{
{
Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"users"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"workspaces"},
Resources: []string{"monitoring/*"},
},
{
Verbs: []string{"list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"quota", "status", "monitoring", "persistentvolumeclaims"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"resources"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces", "workspaces/*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{""},
Resources: []string{"namespaces"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"", "apps", "extensions", "batch"},
Resources: []string{"serviceaccounts", "limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumeclaims", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"rolebindings", "roles"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"members"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"router"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"jenkins.kubesphere.io", "devops.kubesphere.io"},
Resources: []string{"*"},
},
},
},
{
Name: "create",
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"tenant.kubesphere.io"},
Resources: []string{"workspaces"},
},
},
},
{
Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"tenant.kubesphere.io"},
Resources: []string{"workspaces"},
},
},
@@ -349,7 +83,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"tenant.kubesphere.io", "monitoring.kubesphere.io"},
Resources: []string{"workspaces", "workspaces/*"},
},
{
@@ -359,7 +93,7 @@ var (
},
{
Verbs: []string{"*"},
APIGroups: []string{"", "apps", "extensions", "batch"},
APIGroups: []string{"", "apps", "extensions", "batch", "resources.kubesphere.io"},
Resources: []string{"serviceaccounts", "limitranges", "deployments", "configmaps", "secrets", "jobs", "cronjobs", "persistentvolumeclaims", "statefulsets", "daemonsets", "ingresses", "services", "pods/*", "pods", "events", "deployments/scale"},
},
{
@@ -367,16 +101,6 @@ var (
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"rolebindings", "roles"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"members"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"router"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"jenkins.kubesphere.io", "devops.kubesphere.io"},
@@ -391,9 +115,13 @@ var (
Actions: []models.Action{
{Name: "view",
Rules: []v1.PolicyRule{{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"monitoring", "health", "monitoring/*"},
Verbs: []string{"get", "list"},
APIGroups: []string{"monitoring.kubesphere.io"},
Resources: []string{"*"},
}, {
Verbs: []string{"get", "list"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"health"},
}},
},
},
@@ -405,14 +133,14 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "watch", "list"},
APIGroups: []string{"account.kubesphere.io"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users", "users/*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"clusterrules"},
ResourceNames: []string{"mapping"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"rulesmapping"},
ResourceNames: []string{"clusterroles"},
},
{
Verbs: []string{"get", "watch", "list"},
@@ -425,12 +153,12 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"create", "get", "list"},
APIGroups: []string{"account.kubesphere.io"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"account.kubesphere.io"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"clusterrules"},
ResourceNames: []string{"mapping"},
},
@@ -445,7 +173,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list", "update", "patch"},
APIGroups: []string{"account.kubesphere.io"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users"},
},
{
@@ -459,8 +187,8 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete", "deletecollection"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"accounts"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users"},
},
},
},
@@ -483,8 +211,8 @@ var (
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"clusterroles/*"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"clusterroles", "clusterroles/*"},
},
},
},
@@ -527,15 +255,9 @@ var (
APIGroups: []string{"storage.k8s.io"},
Resources: []string{"storageclasses"},
}, {
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"storage-classes"},
Resources: []string{"resources"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"storage/*"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"storageclasses", "storageclasses/*"},
},
},
},
@@ -578,15 +300,13 @@ var (
Resources: []string{"nodes", "events"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"nodes"},
Resources: []string{"resources", "monitoring", "monitoring/*"},
Verbs: []string{"get", "list"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"nodes", "nodes/*"},
}, {
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"pods"},
Resources: []string{"resources"},
Verbs: []string{"get", "list"},
APIGroups: []string{"monitoring.kubesphere.io"},
Resources: []string{"nodes"},
},
},
},
@@ -669,14 +389,9 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"list", "get"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"components", "components/*"},
},
{
Verbs: []string{"list", "get"},
APIGroups: []string{""},
Resources: []string{"pods"},
},
},
},
},
@@ -726,12 +441,12 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"rbac.authorization.k8s.io"},
APIGroups: []string{"rbac.authorization.k8s.io", "resources.kubesphere.io"},
Resources: []string{"rolebindings"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"account.kubesphere.io"},
APIGroups: []string{"iam.kubesphere.io"},
Resources: []string{"users"},
},
},
@@ -772,15 +487,9 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"rbac.authorization.k8s.io"},
APIGroups: []string{"rbac.authorization.k8s.io", "resources.kubesphere.io"},
Resources: []string{"roles"},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"roles"},
Resources: []string{"resources"},
},
},
},
{Name: "create",
@@ -819,7 +528,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"apps", "extensions"},
APIGroups: []string{"apps", "extensions", "resources.kubesphere.io"},
Resources: []string{"deployments", "deployments/scale"},
},
{
@@ -875,7 +584,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"apps"},
APIGroups: []string{"apps", "resources.kubesphere.io"},
Resources: []string{"statefulsets"},
},
{
@@ -929,7 +638,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"apps", "extensions"},
APIGroups: []string{"apps", "extensions", "resources.kubesphere.io"},
Resources: []string{"daemonsets"},
},
{
@@ -974,8 +683,17 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"pod/shell"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"pod/terminal"},
},
},
},
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"pods"},
},
},
},
@@ -997,7 +715,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"list", "get"},
APIGroups: []string{""},
APIGroups: []string{"", "resources.kubesphere.io"},
Resources: []string{"services"},
},
},
@@ -1039,7 +757,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"router"},
},
},
@@ -1048,7 +766,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"router"},
},
},
@@ -1057,7 +775,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"update", "patch"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"router"},
},
},
@@ -1066,7 +784,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"router"},
},
},
@@ -1081,7 +799,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{"extensions"},
APIGroups: []string{"extensions", "resources.kubesphere.io"},
Resources: []string{"ingresses"},
},
},
@@ -1121,7 +839,7 @@ var (
Rules: []v1.PolicyRule{
{
Verbs: []string{"get", "list"},
APIGroups: []string{""},
APIGroups: []string{"", "resources.kubesphere.io"},
Resources: []string{"persistentvolumeclaims"},
},
},
@@ -1160,10 +878,9 @@ var (
{Name: "view",
Rules: []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"applications"},
Resources: []string{"resources"},
Verbs: []string{"get", "list"},
APIGroups: []string{"resources.kubesphere.io"},
Resources: []string{"applications"},
},
{
Verbs: []string{"list"},
@@ -1203,7 +920,7 @@ var (
{Name: "view", Rules: []v1.PolicyRule{
{
Verbs: []string{"view", "list"},
APIGroups: []string{"batch"},
APIGroups: []string{"batch", "resources.kubesphere.io"},
Resources: []string{"jobs"},
},
}},
@@ -1236,7 +953,7 @@ var (
{Name: "view", Rules: []v1.PolicyRule{
{
Verbs: []string{"view", "list"},
APIGroups: []string{"batch"},
APIGroups: []string{"batch", "resources.kubesphere.io"},
Resources: []string{"cronjobs"},
},
}},
@@ -1269,7 +986,7 @@ var (
{Name: "view", Rules: []v1.PolicyRule{
{
Verbs: []string{"view", "list"},
APIGroups: []string{""},
APIGroups: []string{"", "resources.kubesphere.io"},
Resources: []string{"secrets"},
},
}},
@@ -1302,7 +1019,7 @@ var (
{Name: "view", Rules: []v1.PolicyRule{
{
Verbs: []string{"view", "list"},
APIGroups: []string{""},
APIGroups: []string{"", "resources.kubesphere.io"},
Resources: []string{"configmaps"},
},
}},
@@ -1331,3 +1048,16 @@ var (
},
}
)
func GetClusterAction(module, action string) (models.Action, error) {
for _, rule := range ClusterRoleRuleMapping {
if rule.Name == module {
for _, act := range rule.Actions {
if act.Name == action {
return act, nil
}
}
}
}
return models.Action{}, fmt.Errorf("not found")
}

View File

@@ -27,4 +27,4 @@ const (
QueryLevelWorkload
QueryLevelPod
QueryLevelContainer
)
)

View File

@@ -248,7 +248,7 @@ func FluentbitOutputInsert(output fb.OutputPlugin) *FluentbitOutputsResult {
// 1. Update ConfigMap
var outputs []fb.OutputPlugin
outputs, err := GetFluentbitOutputFromConfigMap()
if err != nil {
if err != nil {
// If the ConfigMap doesn't exist, a new one will be created later
glog.Errorln(err)
}

View File

@@ -1,18 +1,18 @@
/*
Copyright 2019 The KubeSphere Authors.
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
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
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.
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.
*/
@@ -802,12 +802,12 @@ func MonitorOneWorkspaceStatistics(wsName string) *FormatedLevelMetric {
}()
go func() {
members, errMemb := workspaces.GetOrgMembers(wsName)
count, errMemb := workspaces.WorkspaceUserCount(wsName)
if errMemb != nil {
glog.Errorln(errMemb.Error())
}
// add member metric
memberMetrics = getSpecificMetricItem(timestamp, MetricNameWorkspaceMemberCount, WorkspaceResourceKindMember, len(members), errMemb)
memberMetrics = getSpecificMetricItem(timestamp, MetricNameWorkspaceMemberCount, WorkspaceResourceKindMember, count, errMemb)
wg.Done()
}()

View File

@@ -1,17 +1,19 @@
/*
Copyright 2018 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
Copyright 2019 The KubeSphere Authors.
http://www.apache.org/licenses/LICENSE-2.0
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.
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 metrics
@@ -255,6 +257,7 @@ func ReformatJson(metric string, metricsName string, needAddParams map[string]st
var formatMetric FormatedMetric
err := jsonIter.Unmarshal([]byte(metric), &formatMetric)
if err != nil {
glog.Errorln("Unmarshal metric json failed", err.Error(), metric)
}

View File

@@ -55,9 +55,9 @@ func getUsage(namespace, resource string) (int, error) {
var result *models.PageableResponse
var err error
if resource == resources.Namespaces || resource == resources.StorageClasses {
result, err = resources.ListClusterResource(resource, &params.Conditions{}, "", false, 1, 0)
result, err = resources.ListResources("", resource, &params.Conditions{}, "", false, 1, 0)
} else {
result, err = resources.ListNamespaceResource(namespace, resource, &params.Conditions{}, "", false, 1, 0)
result, err = resources.ListResources(namespace, resource, &params.Conditions{}, "", false, 1, 0)
}
if err != nil {

View File

@@ -20,6 +20,7 @@ package resources
import (
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"sort"
"strings"
@@ -30,16 +31,34 @@ import (
type clusterRoleSearcher struct {
}
func (*clusterRoleSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Rbac().V1().ClusterRoles().Lister().Get(name)
}
// exactly Match
func (*clusterRoleSearcher) match(match map[string]string, item *rbac.ClusterRole) bool {
for k, v := range match {
switch k {
case ownerKind:
fallthrough
case ownerName:
kind := match[ownerKind]
name := match[ownerName]
if !k8sutil.IsControlledBy(item.OwnerReferences, kind, name) {
return false
}
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -62,10 +81,6 @@ func (*clusterRoleSearcher) fuzzy(fuzzy map[string]string, item *rbac.ClusterRol
return false
}
return false
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -77,7 +92,7 @@ func (*clusterRoleSearcher) fuzzy(fuzzy map[string]string, item *rbac.ClusterRol
func (*clusterRoleSearcher) compare(a, b *rbac.ClusterRole, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
@@ -86,7 +101,7 @@ func (*clusterRoleSearcher) compare(a, b *rbac.ClusterRole, orderBy string) bool
}
}
func (s *clusterRoleSearcher) search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
func (s *clusterRoleSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
clusterRoles, err := informers.SharedInformerFactory().Rbac().V1().ClusterRoles().Lister().List(labels.Everything())
if err != nil {

View File

@@ -30,6 +30,10 @@ import (
type configMapSearcher struct {
}
func (*configMapSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().ConfigMaps().Lister().ConfigMaps(namespace).Get(name)
}
// exactly Match
func (*configMapSearcher) match(match map[string]string, item *v1.ConfigMap) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*configMapSearcher) match(match map[string]string, item *v1.ConfigMap) boo
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*configMapSearcher) fuzzy(fuzzy map[string]string, item *v1.ConfigMap) boo
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*configMapSearcher) fuzzy(fuzzy map[string]string, item *v1.ConfigMap) boo
func (*configMapSearcher) compare(a, b *v1.ConfigMap, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -31,6 +31,10 @@ import (
type cronJobSearcher struct {
}
func (*cronJobSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Batch().V1beta1().CronJobs().Lister().CronJobs(namespace).Get(name)
}
func cronJobStatus(item *v1beta1.CronJob) string {
if item.Spec.Suspend != nil && *item.Spec.Suspend {
return paused
@@ -42,12 +46,22 @@ func cronJobStatus(item *v1beta1.CronJob) string {
func (*cronJobSearcher) match(match map[string]string, item *v1beta1.CronJob) bool {
for k, v := range match {
switch k {
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case status:
if cronJobStatus(item) != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -74,10 +88,6 @@ func (*cronJobSearcher) fuzzy(fuzzy map[string]string, item *v1beta1.CronJob) bo
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -98,7 +108,7 @@ func (*cronJobSearcher) compare(a, b *v1beta1.CronJob, orderBy string) bool {
return false
}
return a.Status.LastScheduleTime.Before(b.Status.LastScheduleTime)
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
default:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type daemonSetSearcher struct {
}
func (*daemonSetSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Apps().V1().DaemonSets().Lister().DaemonSets(namespace).Get(name)
}
func daemonSetStatus(item *v1.DaemonSet) string {
if item.Status.NumberAvailable == 0 {
return stopped
@@ -48,8 +52,18 @@ func (*daemonSetSearcher) match(match map[string]string, item *v1.DaemonSet) boo
if daemonSetStatus(item) != v {
return false
}
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -76,10 +90,6 @@ func (*daemonSetSearcher) fuzzy(fuzzy map[string]string, item *v1.DaemonSet) boo
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -92,7 +102,7 @@ func (*daemonSetSearcher) fuzzy(fuzzy map[string]string, item *v1.DaemonSet) boo
func (*daemonSetSearcher) compare(a, b *v1.DaemonSet, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -31,6 +31,10 @@ import (
type deploymentSearcher struct {
}
func (*deploymentSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Apps().V1().Deployments().Lister().Deployments(namespace).Get(name)
}
func deploymentStatus(item *v1.Deployment) string {
if item.Spec.Replicas != nil {
if item.Status.ReadyReplicas == 0 && *item.Spec.Replicas == 0 {
@@ -52,8 +56,18 @@ func (*deploymentSearcher) match(match map[string]string, item *v1.Deployment) b
if deploymentStatus(item) != v {
return false
}
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -80,10 +94,6 @@ func (*deploymentSearcher) fuzzy(fuzzy map[string]string, item *v1.Deployment) b
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -96,7 +106,7 @@ func (*deploymentSearcher) fuzzy(fuzzy map[string]string, item *v1.Deployment) b
func (*deploymentSearcher) compare(a, b *v1.Deployment, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -31,6 +31,10 @@ import (
type ingressSearcher struct {
}
func (*ingressSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Extensions().V1beta1().Ingresses().Lister().Ingresses(namespace).Get(name)
}
// exactly Match
func (*ingressSearcher) match(match map[string]string, item *extensions.Ingress) bool {
for k, v := range match {
@@ -39,8 +43,14 @@ func (*ingressSearcher) match(match map[string]string, item *extensions.Ingress)
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -67,10 +77,6 @@ func (*ingressSearcher) fuzzy(fuzzy map[string]string, item *extensions.Ingress)
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -82,7 +88,7 @@ func (*ingressSearcher) fuzzy(fuzzy map[string]string, item *extensions.Ingress)
func (*ingressSearcher) compare(a, b *extensions.Ingress, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -32,6 +32,10 @@ import (
type jobSearcher struct {
}
func (*jobSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Batch().V1().Jobs().Lister().Jobs(namespace).Get(name)
}
func jobStatus(item *batchv1.Job) string {
status := ""
@@ -54,8 +58,18 @@ func (*jobSearcher) match(match map[string]string, item *batchv1.Job) bool {
if jobStatus(item) != v {
return false
}
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -82,10 +96,6 @@ func (*jobSearcher) fuzzy(fuzzy map[string]string, item *batchv1.Job) bool {
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -111,6 +121,8 @@ func jobUpdateTime(item *batchv1.Job) time.Time {
func (*jobSearcher) compare(a, b *batchv1.Job, orderBy string) bool {
switch orderBy {
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case updateTime:
return jobUpdateTime(a).Before(jobUpdateTime(b))
case name:

View File

@@ -30,6 +30,10 @@ import (
type namespaceSearcher struct {
}
func (*namespaceSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().Namespaces().Lister().Get(name)
}
// exactly Match
func (*namespaceSearcher) match(match map[string]string, item *v1.Namespace) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*namespaceSearcher) match(match map[string]string, item *v1.Namespace) boo
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*namespaceSearcher) fuzzy(fuzzy map[string]string, item *v1.Namespace) boo
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*namespaceSearcher) fuzzy(fuzzy map[string]string, item *v1.Namespace) boo
func (*namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
@@ -90,7 +96,7 @@ func (*namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool {
}
}
func (s *namespaceSearcher) search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
func (s *namespaceSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
namespaces, err := informers.SharedInformerFactory().Core().V1().Namespaces().Lister().List(labels.Everything())
if err != nil {

View File

@@ -30,6 +30,10 @@ import (
type nodeSearcher struct {
}
func (*nodeSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().Nodes().Lister().Get(name)
}
// exactly Match
func (*nodeSearcher) match(match map[string]string, item *v1.Node) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*nodeSearcher) match(match map[string]string, item *v1.Node) bool {
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*nodeSearcher) fuzzy(fuzzy map[string]string, item *v1.Node) bool {
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*nodeSearcher) fuzzy(fuzzy map[string]string, item *v1.Node) bool {
func (*nodeSearcher) compare(a, b *v1.Node, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
@@ -90,7 +96,7 @@ func (*nodeSearcher) compare(a, b *v1.Node, orderBy string) bool {
}
}
func (s *nodeSearcher) search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
func (s *nodeSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
nodes, err := informers.SharedInformerFactory().Core().V1().Nodes().Lister().List(labels.Everything())
if err != nil {

View File

@@ -30,6 +30,10 @@ import (
type persistentVolumeClaimSearcher struct {
}
func (*persistentVolumeClaimSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().PersistentVolumeClaims().Lister().PersistentVolumeClaims(namespace).Get(name)
}
// exactly Match
func (*persistentVolumeClaimSearcher) match(match map[string]string, item *v1.PersistentVolumeClaim) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*persistentVolumeClaimSearcher) match(match map[string]string, item *v1.Pe
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*persistentVolumeClaimSearcher) fuzzy(fuzzy map[string]string, item *v1.Pe
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*persistentVolumeClaimSearcher) fuzzy(fuzzy map[string]string, item *v1.Pe
func (*persistentVolumeClaimSearcher) compare(a, b *v1.PersistentVolumeClaim, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -31,25 +31,29 @@ import (
type podSearcher struct {
}
func podBelongTo(item *v1.Pod, kind string, name string) bool {
func (*podSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().Pods().Lister().Pods(namespace).Get(name)
}
if strings.EqualFold(kind, "Deployment") {
func podBelongTo(item *v1.Pod, kind string, name string) bool {
switch kind {
case "Deployment":
if podBelongToDeployment(item, name) {
return true
}
} else if strings.EqualFold(kind, "ReplicaSet") {
case "ReplicaSet":
if podBelongToReplicaSet(item, name) {
return true
}
} else if strings.EqualFold(kind, "DaemonSet") {
case "DaemonSet":
if podBelongToDaemonSet(item, name) {
return true
}
} else if strings.EqualFold(kind, "StatefulSet") {
case "StatefulSet":
if podBelongToStatefulSet(item, name) {
return true
}
} else if strings.EqualFold(kind, "Job") {
case "Job":
if podBelongToJob(item, name) {
return true
}
@@ -57,9 +61,9 @@ func podBelongTo(item *v1.Pod, kind string, name string) bool {
return false
}
func replicaSetBelongToDeployment(replicaSet *v12.ReplicaSet, name string) bool {
func replicaSetBelongToDeployment(replicaSet *v12.ReplicaSet, deploymentName string) bool {
for _, owner := range replicaSet.OwnerReferences {
if owner.Kind == "Deployment" && owner.Name == name {
if owner.Kind == "Deployment" && owner.Name == deploymentName {
return true
}
}
@@ -84,38 +88,36 @@ func podBelongToJob(item *v1.Pod, name string) bool {
return false
}
func podBelongToReplicaSet(item *v1.Pod, name string) bool {
func podBelongToReplicaSet(item *v1.Pod, replicaSetName string) bool {
for _, owner := range item.OwnerReferences {
if owner.Kind == "ReplicaSet" && owner.Name == name {
if owner.Kind == "ReplicaSet" && owner.Name == replicaSetName {
return true
}
}
return false
}
func podBelongToStatefulSet(item *v1.Pod, name string) bool {
replicas, err := informers.SharedInformerFactory().Apps().V1().ReplicaSets().Lister().ReplicaSets(item.Namespace).List(labels.Everything())
if err != nil {
return false
}
for _, r := range replicas {
if replicaSetBelongToDeployment(r, name) {
return podBelongToReplicaSet(item, r.Name)
func podBelongToStatefulSet(item *v1.Pod, statefulSetName string) bool {
for _, owner := range item.OwnerReferences {
if owner.Kind == "StatefulSet" && owner.Name == statefulSetName {
return true
}
}
return false
}
func podBelongToDeployment(item *v1.Pod, name string) bool {
func podBelongToDeployment(item *v1.Pod, deploymentName string) bool {
replicas, err := informers.SharedInformerFactory().Apps().V1().ReplicaSets().Lister().ReplicaSets(item.Namespace).List(labels.Everything())
if err != nil {
return false
}
for _, r := range replicas {
if replicaSetBelongToDeployment(r, name) {
return podBelongToReplicaSet(item, r.Name)
if replicaSetBelongToDeployment(r, deploymentName) && podBelongToReplicaSet(item, r.Name) {
return true
}
}
return false
}
@@ -134,10 +136,10 @@ func podBelongToService(item *v1.Pod, serviceName string) bool {
if err != nil {
return false
}
for k, v := range service.Spec.Selector {
if item.Labels[k] != v {
return false
}
selector := labels.Set(service.Spec.Selector).AsSelectorPreValidated()
if !selector.Matches(labels.Set(item.Labels)) {
return false
}
return true
}
@@ -146,11 +148,11 @@ func podBelongToService(item *v1.Pod, serviceName string) bool {
func (*podSearcher) match(match map[string]string, item *v1.Pod) bool {
for k, v := range match {
switch k {
case "ownerKind":
case ownerKind:
fallthrough
case "ownerName":
kind := match["ownerKind"]
name := match["ownerName"]
case ownerName:
kind := match[ownerKind]
name := match[ownerName]
if !podBelongTo(item, kind, name) {
return false
}
@@ -170,6 +172,10 @@ func (*podSearcher) match(match map[string]string, item *v1.Pod) bool {
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if item.Labels[k] != v {
return false
@@ -200,10 +206,6 @@ func (*podSearcher) fuzzy(fuzzy map[string]string, item *v1.Pod) bool {
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -215,7 +217,7 @@ func (*podSearcher) fuzzy(fuzzy map[string]string, item *v1.Pod) bool {
func (*podSearcher) compare(a, b *v1.Pod, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -21,39 +21,45 @@ import (
"fmt"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"strings"
)
func init() {
namespacedResources[ConfigMaps] = &configMapSearcher{}
namespacedResources[CronJobs] = &cronJobSearcher{}
namespacedResources[DaemonSets] = &daemonSetSearcher{}
namespacedResources[Deployments] = &deploymentSearcher{}
namespacedResources[Ingresses] = &ingressSearcher{}
namespacedResources[Jobs] = &jobSearcher{}
namespacedResources[PersistentVolumeClaims] = &persistentVolumeClaimSearcher{}
namespacedResources[Secrets] = &secretSearcher{}
namespacedResources[Services] = &serviceSearcher{}
namespacedResources[StatefulSets] = &statefulSetSearcher{}
namespacedResources[Pods] = &podSearcher{}
namespacedResources[Roles] = &roleSearcher{}
namespacedResources[S2iBuilders] = &s2iBuilderSearcher{}
namespacedResources[S2iRuns] = &s2iRunSearcher{}
resources[ConfigMaps] = &configMapSearcher{}
resources[CronJobs] = &cronJobSearcher{}
resources[DaemonSets] = &daemonSetSearcher{}
resources[Deployments] = &deploymentSearcher{}
resources[Ingresses] = &ingressSearcher{}
resources[Jobs] = &jobSearcher{}
resources[PersistentVolumeClaims] = &persistentVolumeClaimSearcher{}
resources[Secrets] = &secretSearcher{}
resources[Services] = &serviceSearcher{}
resources[StatefulSets] = &statefulSetSearcher{}
resources[Pods] = &podSearcher{}
resources[Roles] = &roleSearcher{}
resources[S2iBuilders] = &s2iBuilderSearcher{}
resources[S2iRuns] = &s2iRunSearcher{}
clusterResources[Nodes] = &nodeSearcher{}
clusterResources[Namespaces] = &namespaceSearcher{}
clusterResources[ClusterRoles] = &clusterRoleSearcher{}
clusterResources[StorageClasses] = &storageClassesSearcher{}
clusterResources[S2iBuilderTemplates] = &s2iBuilderTemplateSearcher{}
resources[Nodes] = &nodeSearcher{}
resources[Namespaces] = &namespaceSearcher{}
resources[ClusterRoles] = &clusterRoleSearcher{}
resources[StorageClasses] = &storageClassesSearcher{}
resources[S2iBuilderTemplates] = &s2iBuilderTemplateSearcher{}
resources[Workspaces] = &workspaceSearcher{}
}
var namespacedResources = make(map[string]namespacedSearcherInterface)
var clusterResources = make(map[string]clusterSearcherInterface)
var (
resources = make(map[string]resourceSearchInterface)
clusterResources = []string{Nodes, Workspaces, Namespaces, ClusterRoles, StorageClasses, S2iBuilderTemplates}
)
const (
name = "name"
label = "label"
createTime = "createTime"
ownerKind = "ownerKind"
ownerName = "ownerName"
CreateTime = "CreateTime"
updateTime = "updateTime"
lastScheduleTime = "lastScheduleTime"
displayName = "displayName"
@@ -72,6 +78,8 @@ const (
Deployments = "deployments"
DaemonSets = "daemonsets"
Roles = "roles"
Workspaces = "workspaces"
WorkspaceRoles = "workspaceroles"
CronJobs = "cronjobs"
ConfigMaps = "configmaps"
Ingresses = "ingresses"
@@ -90,72 +98,58 @@ const (
S2iRuns = "s2iruns"
)
type namespacedSearcherInterface interface {
type resourceSearchInterface interface {
get(namespace, name string) (interface{}, error)
search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error)
}
type clusterSearcherInterface interface {
search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error)
func ListResourcesByName(namespace, resource string, names []string) (*models.PageableResponse, error) {
items := make([]interface{}, 0)
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)
}
} else {
return nil, fmt.Errorf("not found")
}
return &models.PageableResponse{TotalCount: len(items), Items: items}, nil
}
func ListNamespaceResource(namespace, resource string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
func ListResources(namespace, resource string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
items := make([]interface{}, 0)
total := 0
var err error
var result []interface{}
if searcher, ok := namespacedResources[resource]; ok {
// none namespace resource
if namespace != "" && sliceutil.HasString(clusterResources, resource) {
return nil, fmt.Errorf("not found")
}
if searcher, ok := resources[resource]; ok {
result, err = searcher.search(namespace, conditions, orderBy, reverse)
} else {
return nil, fmt.Errorf("not support")
return nil, fmt.Errorf("not found")
}
if err != nil {
return nil, err
}
total = len(result)
for i, d := range result {
if i >= offset && (limit == -1 || len(items) < limit) {
items = append(items, d)
}
}
return &models.PageableResponse{TotalCount: total, Items: items}, nil
}
func ListClusterResource(resource string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
items := make([]interface{}, 0)
total := 0
var err error
if err != nil {
return nil, err
}
var result []interface{}
if searcher, ok := clusterResources[resource]; ok {
result, err = searcher.search(conditions, orderBy, reverse)
} else if searcher, ok := namespacedResources[resource]; ok {
result, err = searcher.search("", conditions, orderBy, reverse)
} else {
return nil, fmt.Errorf("not support")
}
if err != nil {
return nil, err
}
total = len(result)
for i, d := range result {
if i >= offset && len(items) < limit {
items = append(items, d)
}
}
return &models.PageableResponse{TotalCount: total, Items: items}, nil
return &models.PageableResponse{TotalCount: len(result), Items: items}, nil
}
func searchFuzzy(m map[string]string, key, value string) bool {

View File

@@ -30,6 +30,10 @@ import (
type roleSearcher struct {
}
func (*roleSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Rbac().V1().Roles().Lister().Roles(namespace).Get(name)
}
// exactly Match
func (*roleSearcher) match(match map[string]string, item *rbac.Role) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*roleSearcher) match(match map[string]string, item *rbac.Role) bool {
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -62,10 +72,6 @@ func (*roleSearcher) fuzzy(fuzzy map[string]string, item *rbac.Role) bool {
return false
}
return false
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -77,7 +83,7 @@ func (*roleSearcher) fuzzy(fuzzy map[string]string, item *rbac.Role) bool {
func (*roleSearcher) compare(a, b *rbac.Role, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type s2iBuilderSearcher struct {
}
func (*s2iBuilderSearcher) get(namespace, name string) (interface{}, error) {
return informers.S2iSharedInformerFactory().Devops().V1alpha1().S2iBuilders().Lister().S2iBuilders(namespace).Get(name)
}
// exactly Match
func (*s2iBuilderSearcher) match(match map[string]string, item *v1alpha1.S2iBuilder) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*s2iBuilderSearcher) match(match map[string]string, item *v1alpha1.S2iBuil
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*s2iBuilderSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1.S2iBuil
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*s2iBuilderSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1.S2iBuil
func (*s2iBuilderSearcher) compare(a, b *v1alpha1.S2iBuilder, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type s2iBuilderTemplateSearcher struct {
}
func (*s2iBuilderTemplateSearcher) get(namespace, name string) (interface{}, error) {
return informers.S2iSharedInformerFactory().Devops().V1alpha1().S2iBuilderTemplates().Lister().Get(name)
}
// exactly Match
func (*s2iBuilderTemplateSearcher) match(match map[string]string, item *v1alpha1.S2iBuilderTemplate) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*s2iBuilderTemplateSearcher) match(match map[string]string, item *v1alpha1
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -62,10 +72,6 @@ func (*s2iBuilderTemplateSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1
return false
}
return false
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -77,7 +83,7 @@ func (*s2iBuilderTemplateSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1
func (*s2iBuilderTemplateSearcher) compare(a, b *v1alpha1.S2iBuilderTemplate, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
@@ -86,7 +92,7 @@ func (*s2iBuilderTemplateSearcher) compare(a, b *v1alpha1.S2iBuilderTemplate, or
}
}
func (s *s2iBuilderTemplateSearcher) search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
func (s *s2iBuilderTemplateSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
builderTemplates, err := informers.S2iSharedInformerFactory().Devops().V1alpha1().S2iBuilderTemplates().Lister().List(labels.Everything())
if err != nil {

View File

@@ -33,6 +33,10 @@ import (
type s2iRunSearcher struct {
}
func (*s2iRunSearcher) get(namespace, name string) (interface{}, error) {
return informers.S2iSharedInformerFactory().Devops().V1alpha1().S2iRuns().Lister().S2iRuns(namespace).Get(name)
}
// exactly Match
func (*s2iRunSearcher) match(match map[string]string, item *v1alpha1.S2iRun) bool {
for k, v := range match {
@@ -42,11 +46,17 @@ func (*s2iRunSearcher) match(match map[string]string, item *v1alpha1.S2iRun) boo
return false
}
case status:
if string(item.Status.RunState) != v{
if string(item.Status.RunState) != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -73,10 +83,6 @@ func (*s2iRunSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1.S2iRun) boo
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -88,7 +94,7 @@ func (*s2iRunSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1.S2iRun) boo
func (*s2iRunSearcher) compare(a, b *v1alpha1.S2iRun, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type secretSearcher struct {
}
func (*secretSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().Secrets().Lister().Secrets(namespace).Get(name)
}
// exactly Match
func (*secretSearcher) match(match map[string]string, item *v1.Secret) bool {
for k, v := range match {
@@ -42,8 +46,14 @@ func (*secretSearcher) match(match map[string]string, item *v1.Secret) bool {
if string(item.Type) != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -70,10 +80,6 @@ func (*secretSearcher) fuzzy(fuzzy map[string]string, item *v1.Secret) bool {
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -85,7 +91,7 @@ func (*secretSearcher) fuzzy(fuzzy map[string]string, item *v1.Secret) bool {
func (*secretSearcher) compare(a, b *v1.Secret, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type serviceSearcher struct {
}
func (*serviceSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Core().V1().Services().Lister().Services(namespace).Get(name)
}
// exactly Match
func (*serviceSearcher) match(match map[string]string, item *v1.Service) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*serviceSearcher) match(match map[string]string, item *v1.Service) bool {
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -66,10 +76,6 @@ func (*serviceSearcher) fuzzy(fuzzy map[string]string, item *v1.Service) bool {
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -81,7 +87,7 @@ func (*serviceSearcher) fuzzy(fuzzy map[string]string, item *v1.Service) bool {
func (*serviceSearcher) compare(a, b *v1.Service, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type statefulSetSearcher struct {
}
func (*statefulSetSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(name)
}
func statefulSetStatus(item *v1.StatefulSet) string {
if item.Spec.Replicas != nil {
if item.Status.ReadyReplicas == 0 && *item.Spec.Replicas == 0 {
@@ -52,7 +56,9 @@ func (*statefulSetSearcher) match(match map[string]string, item *v1.StatefulSet)
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -95,7 +101,7 @@ func (*statefulSetSearcher) fuzzy(fuzzy map[string]string, item *v1.StatefulSet)
func (*statefulSetSearcher) compare(a, b *v1.StatefulSet, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough

View File

@@ -30,6 +30,10 @@ import (
type storageClassesSearcher struct {
}
func (*storageClassesSearcher) get(namespace, name string) (interface{}, error) {
return informers.SharedInformerFactory().Storage().V1().StorageClasses().Lister().Get(name)
}
// exactly Match
func (*storageClassesSearcher) match(match map[string]string, item *v1.StorageClass) bool {
for k, v := range match {
@@ -38,8 +42,14 @@ func (*storageClassesSearcher) match(match map[string]string, item *v1.StorageCl
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
return false
if item.Labels[k] != v {
return false
}
}
}
return true
@@ -62,10 +72,6 @@ func (*storageClassesSearcher) fuzzy(fuzzy map[string]string, item *v1.StorageCl
return false
}
return false
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
@@ -77,7 +83,7 @@ func (*storageClassesSearcher) fuzzy(fuzzy map[string]string, item *v1.StorageCl
func (*storageClassesSearcher) compare(a, b *v1.StorageClass, orderBy string) bool {
switch orderBy {
case createTime:
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
@@ -86,7 +92,7 @@ func (*storageClassesSearcher) compare(a, b *v1.StorageClass, orderBy string) bo
}
}
func (s *storageClassesSearcher) search(conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
func (s *storageClassesSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
storageClasses, err := informers.SharedInformerFactory().Storage().V1().StorageClasses().Lister().List(labels.Everything())
if err != nil {

View File

@@ -0,0 +1,132 @@
/*
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 resources
import (
tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/params"
"sort"
"strings"
"k8s.io/apimachinery/pkg/labels"
)
type workspaceSearcher struct {
}
func (*workspaceSearcher) get(namespace, name string) (interface{}, error) {
return informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(name)
}
// exactly Match
func (*workspaceSearcher) match(match map[string]string, item *tenantv1alpha1.Workspace) bool {
for k, v := range match {
switch k {
case name:
if item.Name != v && item.Labels[displayName] != v {
return false
}
case keyword:
if !strings.Contains(item.Name, v) && !searchFuzzy(item.Labels, "", v) && !searchFuzzy(item.Annotations, "", v) {
return false
}
default:
if item.Labels[k] != v {
return false
}
}
}
return true
}
// Fuzzy searchInNamespace
func (*workspaceSearcher) fuzzy(fuzzy map[string]string, item *tenantv1alpha1.Workspace) bool {
for k, v := range fuzzy {
switch k {
case name:
if !strings.Contains(item.Name, v) && !strings.Contains(item.Labels[displayName], v) {
return false
}
case label:
if !searchFuzzy(item.Labels, "", v) {
return false
}
case annotation:
if !searchFuzzy(item.Annotations, "", v) {
return false
}
return false
case app:
if !strings.Contains(item.Labels[chart], v) && !strings.Contains(item.Labels[release], v) {
return false
}
default:
if !searchFuzzy(item.Labels, k, v) && !searchFuzzy(item.Annotations, k, v) {
return false
}
}
}
return true
}
func (*workspaceSearcher) compare(a, b *tenantv1alpha1.Workspace, orderBy string) bool {
switch orderBy {
case CreateTime:
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case name:
fallthrough
default:
return strings.Compare(a.Name, b.Name) <= 0
}
}
func (s *workspaceSearcher) search(namespace string, conditions *params.Conditions, orderBy string, reverse bool) ([]interface{}, error) {
workspaces, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().List(labels.Everything())
if err != nil {
return nil, err
}
result := make([]*tenantv1alpha1.Workspace, 0)
if len(conditions.Match) == 0 && len(conditions.Fuzzy) == 0 {
result = workspaces
} else {
for _, item := range workspaces {
if s.match(conditions.Match, item) && s.fuzzy(conditions.Fuzzy, item) {
result = append(result, item)
}
}
}
sort.Slice(result, func(i, j int) bool {
if reverse {
tmp := i
i = j
j = tmp
}
return s.compare(result[i], result[j], orderBy)
})
r := make([]interface{}, 0)
for _, i := range result {
r = append(r, i)
}
return r, nil
}

View File

@@ -32,12 +32,9 @@ import (
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/api/rbac/v1"
"strings"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam"
)
func GetAllRouters() ([]*corev1.Service, error) {
@@ -54,39 +51,6 @@ func GetAllRouters() ([]*corev1.Service, error) {
return services, nil
}
func GetAllRoutersOfUser(username string) ([]*corev1.Service, error) {
allNamespace, namespaces, err := iam.GetUserNamespaces(username, v1.PolicyRule{
Verbs: []string{"get", "list"},
APIGroups: []string{""},
Resources: []string{"services"},
})
// return by cluster role
if err != nil {
glog.Error(err)
return nil, err
}
if allNamespace {
return GetAllRouters()
}
routers := make([]*corev1.Service, 0)
for _, namespace := range namespaces {
router, err := GetRouter(namespace)
if err != nil {
glog.Error(err)
return routers, err
} else if router != nil {
routers = append(routers, router)
}
}
return routers, nil
}
// Get router from a namespace
func GetRouter(namespace string) (*corev1.Service, error) {
serviceName := constants.IngressControllerPrefix + namespace

View File

@@ -40,7 +40,7 @@ func GetNamespacesResourceStatus(namespace string) (*workLoadStatus, error) {
notReadyStatus = "pending"
}
notReadyList, err = resources.ListNamespaceResource(namespace, resource, &params.Conditions{Match: map[string]string{"status": notReadyStatus}}, "", false, -1, 0)
notReadyList, err = resources.ListResources(namespace, resource, &params.Conditions{Match: map[string]string{"status": notReadyStatus}}, "", false, -1, 0)
if err != nil {
return nil, err

119
pkg/models/tenant/devops.go Normal file
View File

@@ -0,0 +1,119 @@
/*
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 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/mysql"
"net/http"
"sort"
"strings"
)
func ListDevopsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
db := mysql.Client()
var workspaceDOPBindings []models.WorkspaceDPBinding
if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil {
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)
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:]...)
i--
}
}
}
sort.Slice(devOpsProjects, func(i, j int) bool {
switch orderBy {
case "name":
if reverse {
return devOpsProjects[i].Name < devOpsProjects[j].Name
} else {
return devOpsProjects[i].Name > devOpsProjects[j].Name
}
default:
if reverse {
return devOpsProjects[i].CreateTime.After(*devOpsProjects[j].CreateTime)
} else {
return devOpsProjects[i].CreateTime.Before(*devOpsProjects[j].CreateTime)
}
}
})
for i := 0; i < len(devOpsProjects); i++ {
inWorkspace := false
for _, binding := range workspaceDOPBindings {
if binding.DevOpsProject == *devOpsProjects[i].ProjectId {
inWorkspace = true
}
}
if !inWorkspace {
devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...)
i--
}
}
// limit offset
result := make([]interface{}, 0)
for i, v := range devOpsProjects {
if len(result) < limit && i >= offset {
result = append(result, v)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(devOpsProjects)}, nil
}

View File

@@ -0,0 +1,134 @@
/*
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 tenant
import (
"k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/labels"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/params"
"sort"
"strings"
)
type namespaceSearcher struct {
}
// Exactly Match
func (*namespaceSearcher) match(match map[string]string, item *v1.Namespace) bool {
for k, v := range match {
switch k {
default:
if item.Labels[k] != v {
return false
}
}
}
return true
}
func (*namespaceSearcher) fuzzy(fuzzy map[string]string, item *v1.Namespace) bool {
for k, v := range fuzzy {
switch k {
case "name":
if !strings.Contains(item.Name, v) && !strings.Contains(item.Labels["displayName"], v) {
return false
}
default:
return false
}
}
return true
}
func (*namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool {
switch orderBy {
case "createTime":
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case "name":
fallthrough
default:
return strings.Compare(a.Name, b.Name) <= 0
}
}
func (*namespaceSearcher) GetNamespaces(username string) ([]*v1.Namespace, error) {
roles, err := iam.GetUserRoles("", username)
if err != nil {
return nil, err
}
namespaces := make([]*v1.Namespace, 0)
namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
for _, role := range roles {
namespace, err := namespaceLister.Get(role.Namespace)
if err != nil {
return nil, err
}
namespaces = append(namespaces, namespace)
}
return namespaces, nil
}
func (s *namespaceSearcher) search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1.Namespace, error) {
rules, err := iam.GetUserClusterRules(username)
if err != nil {
return nil, err
}
namespaces := make([]*v1.Namespace, 0)
if iam.RulesMatchesRequired(rules, rbacv1.PolicyRule{Verbs: []string{"list"}, APIGroups: []string{"tenant.kubesphere.io"}, Resources: []string{"namespaces"}}) {
namespaces, err = informers.SharedInformerFactory().Core().V1().Namespaces().Lister().List(labels.Everything())
} else {
namespaces, err = s.GetNamespaces(username)
}
if err != nil {
return nil, err
}
result := make([]*v1.Namespace, 0)
for _, namespace := range namespaces {
if s.match(conditions.Match, namespace) && s.fuzzy(conditions.Fuzzy, namespace) {
result = append(result, namespace)
}
}
// order & reverse
sort.Slice(result, func(i, j int) bool {
if reverse {
tmp := i
i = j
j = tmp
}
return s.compare(result[i], result[j], orderBy)
})
return result, nil
}

103
pkg/models/tenant/tenant.go Normal file
View File

@@ -0,0 +1,103 @@
/*
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 tenant
import (
"k8s.io/api/core/v1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models"
ws "kubesphere.io/kubesphere/pkg/models/workspaces"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"strconv"
)
var (
workspaces = workspaceSearcher{}
namespaces = namespaceSearcher{}
)
func CreateNamespace(workspaceName string, namespace *v1.Namespace, username string) (*v1.Namespace, error) {
if namespace.Labels == nil {
namespace.Labels = make(map[string]string, 0)
}
if username != "" {
namespace.Labels[constants.CreatorLabelKey] = username
}
namespace.Labels[constants.WorkspaceLabelKey] = workspaceName
return k8s.Client().CoreV1().Namespaces().Create(namespace)
}
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)
if err != nil {
return nil, err
}
// limit offset
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)
result = append(result, workspace)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(workspaces)}, nil
}
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)
if err != nil {
return nil, err
}
// limit offset
result := make([]interface{}, 0)
for i, v := range namespaces {
if len(result) < limit && i >= offset {
result = append(result, v)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(namespaces)}, nil
}

View File

@@ -0,0 +1,130 @@
/*
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 tenant
import (
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/labels"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/params"
"sort"
"strings"
)
type workspaceSearcher struct {
}
// Exactly Match
func (*workspaceSearcher) match(match map[string]string, item *v1alpha1.Workspace) bool {
for k, v := range match {
switch k {
case "name":
if item.Name != v && item.Labels[constants.DisplayNameLabelKey] != v {
return false
}
default:
if item.Labels[k] != v {
return false
}
}
}
return true
}
func (*workspaceSearcher) fuzzy(fuzzy map[string]string, item *v1alpha1.Workspace) bool {
for k, v := range fuzzy {
switch k {
case "name":
if !strings.Contains(item.Name, v) && !strings.Contains(item.Labels["displayName"], v) {
return false
}
default:
return false
}
}
return true
}
func (*workspaceSearcher) compare(a, b *v1alpha1.Workspace, orderBy string) bool {
switch orderBy {
case "createTime":
return a.CreationTimestamp.Time.Before(b.CreationTimestamp.Time)
case "name":
fallthrough
default:
return strings.Compare(a.Name, b.Name) <= 0
}
}
func (s *workspaceSearcher) search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error) {
rules, err := iam.GetUserClusterRules(username)
if err != nil {
return nil, err
}
workspaces := make([]*v1alpha1.Workspace, 0)
if iam.RulesMatchesRequired(rules, rbacv1.PolicyRule{Verbs: []string{"list"}, APIGroups: []string{"tenant.kubesphere.io"}, Resources: []string{"workspaces"}}) {
workspaces, err = informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().List(labels.Everything())
if err != nil {
return nil, err
}
} else {
workspaceRoles, err := iam.GetUserWorkspaceRoleMap(username)
if err != nil {
return nil, err
}
for k := range workspaceRoles {
workspace, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(k)
if err != nil {
return nil, err
}
workspaces = append(workspaces, workspace)
}
}
result := make([]*v1alpha1.Workspace, 0)
for _, workspace := range workspaces {
if s.match(conditions.Match, workspace) && s.fuzzy(conditions.Fuzzy, workspace) {
result = append(result, workspace)
}
}
// order & reverse
sort.Slice(result, func(i, j int) bool {
if reverse {
tmp := i
i = j
j = tmp
}
return s.compare(result[i], result[j], orderBy)
})
return result, nil
}
func GetWorkspace(workspaceName string) (*v1alpha1.Workspace, error) {
return informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName)
}

View File

@@ -0,0 +1,301 @@
// Copyright 2018 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.
//
// the code is mainly from:
// https://github.com/kubernetes/dashboard/blob/master/src/app/backend/handler/terminal.go
// thanks to the related developer
package terminal
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/golang/glog"
"gopkg.in/igm/sockjs-go.v2/sockjs"
"io"
"k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/remotecommand"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
)
// PtyHandler is what remotecommand expects from a pty
type PtyHandler interface {
io.Reader
io.Writer
remotecommand.TerminalSizeQueue
}
// TerminalSession implements PtyHandler (using a SockJS connection)
type TerminalSession struct {
id string
bound chan error
sockJSSession sockjs.Session
sizeChan chan remotecommand.TerminalSize
}
// TerminalMessage is the messaging protocol between ShellController and TerminalSession.
//
// OP DIRECTION FIELD(S) USED DESCRIPTION
// ---------------------------------------------------------------------
// bind fe->be SessionID Id sent back from TerminalResponse
// stdin fe->be Data Keystrokes/paste buffer
// resize fe->be Rows, Cols New terminal size
// stdout be->fe Data Output from the process
// toast be->fe Data OOB message to be shown to the user
type TerminalMessage struct {
Op, Data, SessionID string
Rows, Cols uint16
}
// TerminalSize handles pty->process resize events
// Called in a loop from remotecommand as long as the process is running
func (t TerminalSession) Next() *remotecommand.TerminalSize {
select {
case size := <-t.sizeChan:
return &size
}
}
// Read handles pty->process messages (stdin, resize)
// Called in a loop from remotecommand as long as the process is running
func (t TerminalSession) Read(p []byte) (int, error) {
m, err := t.sockJSSession.Recv()
if err != nil {
return 0, err
}
var msg TerminalMessage
if err := json.Unmarshal([]byte(m), &msg); err != nil {
return 0, err
}
switch msg.Op {
case "stdin":
return copy(p, msg.Data), nil
case "resize":
t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows}
return 0, nil
default:
return 0, fmt.Errorf("unknown message type '%s'", msg.Op)
}
}
// Write handles process->pty stdout
// Called from remotecommand whenever there is any output
func (t TerminalSession) Write(p []byte) (int, error) {
msg, err := json.Marshal(TerminalMessage{
Op: "stdout",
Data: string(p),
})
if err != nil {
return 0, err
}
if err = t.sockJSSession.Send(string(msg)); err != nil {
return 0, err
}
return len(p), nil
}
// Toast can be used to send the user any OOB messages
// hterm puts these in the center of the terminal
func (t TerminalSession) Toast(p string) error {
msg, err := json.Marshal(TerminalMessage{
Op: "toast",
Data: p,
})
if err != nil {
return err
}
if err = t.sockJSSession.Send(string(msg)); err != nil {
return err
}
return nil
}
// Close shuts down the SockJS connection and sends the status code and reason to the client
// Can happen if the process exits or if there is an error starting up the process
// For now the status code is unused and reason is shown to the user (unless "")
func (t TerminalSession) Close(status uint32, reason string) {
t.sockJSSession.Close(status, reason)
}
// terminalSessions stores a map of all TerminalSession objects
// FIXME: this structure needs locking
var terminalSessions = make(map[string]TerminalSession)
// handleTerminalSession is Called by net/http for any new /api/sockjs connections
func HandleTerminalSession(session sockjs.Session) {
glog.Infof("handleTerminalSession, ID:%s", session.ID())
var (
buf string
err error
msg TerminalMessage
terminalSession TerminalSession
ok bool
)
if buf, err = session.Recv(); err != nil {
glog.Errorf("handleTerminalSession: can't Recv: %v", err)
return
}
if err = json.Unmarshal([]byte(buf), &msg); err != nil {
glog.Errorf("handleTerminalSession: can't UnMarshal (%v): %s", err, buf)
return
}
if msg.Op != "bind" {
glog.Errorf("handleTerminalSession: expected 'bind' message, got: %s", buf)
return
}
if terminalSession, ok = terminalSessions[msg.SessionID]; !ok {
glog.Errorf("handleTerminalSession: can't find session '%s'", msg.SessionID)
return
}
terminalSession.sockJSSession = session
terminalSessions[msg.SessionID] = terminalSession
terminalSession.bound <- nil
}
// startProcess is called by handleAttach
// Executed cmd in the container specified in request and connects it up with the ptyHandler (a session)
func startProcess(namespace, podName, containerName string, cmd []string, ptyHandler PtyHandler) error {
k8sClient := k8s.Client()
cfg, err := k8s.Config()
if err != nil {
return err
}
req := k8sClient.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec")
req.VersionedParams(&v1.PodExecOptions{
Container: containerName,
Command: cmd,
Stdin: true,
Stdout: true,
Stderr: true,
TTY: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(cfg, "POST", req.URL())
if err != nil {
return err
}
err = exec.Stream(remotecommand.StreamOptions{
Stdin: ptyHandler,
Stdout: ptyHandler,
Stderr: ptyHandler,
TerminalSizeQueue: ptyHandler,
Tty: true,
})
if err != nil {
return err
}
return nil
}
// genTerminalSessionId generates a random session ID string. The format is not really interesting.
// This ID is used to identify the session when the client opens the SockJS connection.
// Not the same as the SockJS session id! We can't use that as that is generated
// on the client side and we don't have it yet at this point.
func genTerminalSessionId() (string, error) {
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
id := make([]byte, hex.EncodedLen(len(bytes)))
hex.Encode(id, bytes)
glog.Infof("genTerminalSessionId, id:" + string(id))
return string(id), nil
}
// isValidShell checks if the shell is an allowed one
func isValidShell(validShells []string, shell string) bool {
for _, validShell := range validShells {
if validShell == shell {
return true
}
}
return false
}
// WaitingForConnection is called from apihandler.handleAttach as a goroutine
// Waits for the SockJS connection to be opened by the client the session to be bound in handleTerminalSession
func WaitingForConnection(shell string, namespace, podName, containerName string, sessionId string) {
glog.Infof("WaitingForConnection, ID:%s", sessionId)
session := terminalSessions[sessionId]
select {
case <-session.bound:
close(session.bound)
defer delete(terminalSessions, sessionId)
var err error
validShells := []string{"sh", "bash"}
if isValidShell(validShells, shell) {
cmd := []string{shell}
err = startProcess(namespace, podName, containerName, cmd, session)
} else {
// No shell given or it was not valid: try some shells until one succeeds or all fail
// FIXME: if the first shell fails then the first keyboard event is lost
for _, testShell := range validShells {
cmd := []string{testShell}
if err = startProcess(namespace, podName, containerName, cmd, session); err == nil {
break
}
}
}
if err != nil {
session.Close(2, err.Error())
return
}
session.Close(1, "Process exited")
}
}
func NewSession(shell, namespace, podName, containerName string) (string, error) {
sessionId, err := genTerminalSessionId()
if err != nil {
return "", err
}
terminalSessions[sessionId] = TerminalSession{
id: sessionId,
bound: make(chan error),
sizeChan: make(chan remotecommand.TerminalSize),
}
if err != nil {
return "", err
}
go WaitingForConnection(shell, namespace, podName, containerName, sessionId)
return sessionId, nil
}

View File

@@ -36,15 +36,6 @@ type Workspace struct {
DevopsProjects []string `json:"devops_projects"`
}
type UserInvite struct {
Username string `json:"username"`
Role string `json:"role"`
}
func (g Group) GetCreateTime() (time.Time, error) {
return time.Parse("2006-01-02T15:04:05Z", g.CreateTime)
}
type WorkspaceDPBinding struct {
Workspace string `gorm:"primary_key"`
DevOpsProject string `gorm:"primary_key"`
@@ -76,27 +67,23 @@ type SimpleRule struct {
}
type User struct {
Username string `json:"username"`
//UID string `json:"uid"`
Groups []string `json:"groups,omitempty"`
Password string `json:"password,omitempty"`
CurrentPassword string `json:"current_password,omitempty"`
//Extra map[string]interface{} `json:"extra"`
AvatarUrl string `json:"avatar_url"`
Description string `json:"description"`
Email string `json:"email"`
LastLoginTime string `json:"last_login_time"`
Status int `json:"status"`
ClusterRole string `json:"cluster_role"`
ClusterRules []SimpleRule `json:"cluster_rules"`
Roles map[string]string `json:"roles,omitempty"`
Rules map[string][]SimpleRule `json:"rules,omitempty"`
Role string `json:"role,omitempty"`
RoleBinding string `json:"role_binding,omitempty"`
Lang string `json:"lang,omitempty"`
WorkspaceRoles map[string]string `json:"workspace_roles,omitempty"`
WorkspaceRole string `json:"workspace_role,omitempty"`
WorkspaceRules map[string][]SimpleRule `json:"workspace_rules,omitempty"`
Username string `json:"username"`
Email string `json:"email"`
Lang string `json:"lang,omitempty"`
Description string `json:"description"`
CreateTime time.Time `json:"create_time"`
Groups []string `json:"groups,omitempty"`
Password string `json:"password,omitempty"`
CurrentPassword string `json:"current_password,omitempty"`
AvatarUrl string `json:"avatar_url"`
LastLoginTime string `json:"last_login_time"`
Status int `json:"status"`
ClusterRole string `json:"cluster_role"`
Roles map[string]string `json:"roles,omitempty"`
Role string `json:"role,omitempty"`
RoleBinding string `json:"role_binding,omitempty"`
RoleBindTime *time.Time `json:"role_bind_time,omitempty"`
WorkspaceRole string `json:"workspace_role,omitempty"`
}
type Group struct {
@@ -105,8 +92,6 @@ type Group struct {
Gid string `json:"gid"`
Members []string `json:"members"`
Logo string `json:"logo"`
Creator string `json:"creator"`
CreateTime string `json:"create_time"`
ChildGroups []string `json:"child_groups"`
Description string `json:"description"`
}

View File

@@ -22,9 +22,15 @@ import (
"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"
@@ -32,22 +38,17 @@ import (
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam"
"log"
"strings"
"github.com/jinzhu/gorm"
core "k8s.io/api/core/v1"
"errors"
"regexp"
"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"
"k8s.io/kubernetes/pkg/util/slice"
"github.com/golang/glog"
"sort"
@@ -128,21 +129,13 @@ func CreateDevopsProject(username string, workspace string, devops models.Devops
}
func createDefaultDevopsRoleBinding(workspace string, project models.DevopsProject) error {
admins, err := iam.GetWorkspaceUsers(workspace, constants.WorkspaceAdmin)
if err != nil {
return err
}
admins := []string{""}
for _, admin := range admins {
createDevopsRoleBinding(workspace, *project.ProjectId, admin, constants.DevopsOwner)
}
viewers, err := iam.GetWorkspaceUsers(workspace, constants.WorkspaceViewer)
if err != nil {
return err
}
viewers := []string{""}
for _, viewer := range viewers {
createDevopsRoleBinding(workspace, *project.ProjectId, viewer, constants.DevopsReporter)
@@ -151,33 +144,6 @@ func createDefaultDevopsRoleBinding(workspace string, project models.DevopsProje
return nil
}
func deleteDevopsRoleBinding(workspace string, projectId string, user string) {
projects := make([]string, 0)
if projectId != "" {
projects = append(projects, projectId)
} else {
p, err := GetDevOpsProjects(workspace)
if err != nil {
glog.Warning("delete devops role binding failed", workspace, projectId, user)
return
}
projects = append(projects, p...)
}
for _, project := range projects {
request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/api/v1alpha/projects/%s/members/%s", constants.DevopsAPIServer, project, user), nil)
request.Header.Add("X-Token-Username", "admin")
resp, err := http.DefaultClient.Do(request)
if err != nil || resp.StatusCode > 200 {
glog.Warning("delete devops role binding failed", workspace, project, user)
}
if resp != nil {
resp.Body.Close()
}
}
}
func createDevopsRoleBinding(workspace string, projectId string, user string, role string) {
projects := make([]string, 0)
@@ -242,23 +208,17 @@ func ListNamespaceByUser(workspaceName string, username string, keyword string,
}
})
clusterRoles, err := iam.GetClusterRoles(username)
rules, err := iam.GetUserClusterRules(username)
if err != nil {
return 0, nil, err
}
rules := make([]v1.PolicyRule, 0)
for _, clusterRole := range clusterRoles {
rules = append(rules, clusterRole.Rules...)
}
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.GetRoles(namespaces[i].Name, username)
roles, err := iam.GetUserRoles(namespaces[i].Name, username)
if err != nil {
return 0, nil, err
}
@@ -313,13 +273,12 @@ func DeleteNamespace(workspace string, namespaceName string) error {
if err != nil {
return err
}
if namespace.Labels != nil && namespace.Labels["kubesphere.io/workspace"] == workspace {
if namespace.Labels[constants.WorkspaceLabelKey] == workspace {
deletePolicy := meta_v1.DeletePropagationForeground
return k8s.Client().CoreV1().Namespaces().Delete(namespaceName, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy})
} else {
return errors.New("resource not found")
}
}
func Delete(workspace *models.Workspace) error {
@@ -339,6 +298,7 @@ func Delete(workspace *models.Workspace) error {
return nil
}
// TODO
func release(workspace *models.Workspace) error {
for _, namespace := range workspace.Namespaces {
err := DeleteNamespace(workspace.Name, namespace)
@@ -348,7 +308,7 @@ func release(workspace *models.Workspace) error {
}
for _, devops := range workspace.DevopsProjects {
err := DeleteDevopsProject(workspace.Creator, devops)
err := DeleteDevopsProject("admin", devops)
if err != nil && !strings.Contains(err.Error(), "not found") {
return err
}
@@ -381,30 +341,6 @@ func workspaceRoleRelease(workspace string) error {
return nil
}
func Create(workspace *models.Workspace) (*models.Workspace, error) {
group, err := iam.CreateGroup(workspace.Group)
if err != nil {
return nil, err
}
created := models.Workspace{
Group: *group,
}
created.Members = make([]string, 0)
created.Namespaces = make([]string, 0)
created.DevopsProjects = make([]string, 0)
err = WorkspaceRoleInit(workspace)
if err != nil {
return nil, err
}
return &created, nil
}
func Edit(workspace *models.Workspace) (*models.Workspace, error) {
group, err := iam.UpdateGroup(&workspace.Group)
@@ -418,24 +354,8 @@ func Edit(workspace *models.Workspace) (*models.Workspace, error) {
return workspace, nil
}
func Detail(name string) (*models.Workspace, error) {
conn, err := ldap.Client()
if err != nil {
return nil, err
}
defer conn.Close()
group, err := iam.GroupDetail(name, conn)
if err != nil {
return nil, err
}
db := mysql.Client()
workspace, err := convertGroupToWorkspace(db, *group)
func DescribeWorkspace(workspaceName string) (*v1alpha1.Workspace, error) {
workspace, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName)
if err != nil {
return nil, err
@@ -444,48 +364,6 @@ func Detail(name string) (*models.Workspace, error) {
return workspace, nil
}
// List all workspaces for the current user
func ListWorkspaceByUser(username string, keyword string) ([]*models.Workspace, error) {
clusterRoles, err := iam.GetClusterRoles(username)
if err != nil {
return nil, err
}
rules := make([]v1.PolicyRule, 0)
for _, clusterRole := range clusterRoles {
rules = append(rules, clusterRole.Rules...)
}
workspacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, Verbs: []string{"list", "get"}, Resources: []string{"workspaces"}}
var workspaces []*models.Workspace
if iam.RulesMatchesRequired(rules, workspacesManager) {
workspaces, err = fetch(nil)
} else {
workspaceNames := make([]string, 0)
for _, clusterRole := range clusterRoles {
if groups := regexp.MustCompile(fmt.Sprintf(`^system:(\S+):(%s)$`, strings.Join(constants.WorkSpaceRoles, "|"))).FindStringSubmatch(clusterRole.Name); len(groups) == 3 {
if !slice.ContainsString(workspaceNames, groups[1], nil) {
workspaceNames = append(workspaceNames, groups[1])
}
}
}
workspaces, err = fetch(workspaceNames)
}
if keyword != "" {
for i := 0; i < len(workspaces); i++ {
if !strings.Contains(workspaces[i].Name, keyword) {
workspaces = append(workspaces[:i], workspaces[i+1:]...)
i--
}
}
}
return workspaces, err
}
func fetch(names []string) ([]*models.Workspace, error) {
if names != nil && len(names) == 0 {
@@ -505,7 +383,7 @@ func fetch(names []string) ([]*models.Workspace, error) {
}
defer conn.Close()
for _, name := range names {
group, err := iam.GroupDetail(name, conn)
group, err := iam.DescribeGroup(name)
if err != nil {
return nil, err
}
@@ -527,90 +405,6 @@ func fetch(names []string) ([]*models.Workspace, error) {
return workspaces, nil
}
func ListDevopsProjectsByUser(username string, workspace string, keyword string, orderBy string, reverse bool, limit int, offset int) (int, []models.DevopsProject, error) {
db := mysql.Client()
var workspaceDOPBindings []models.WorkspaceDPBinding
if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil {
return 0, 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)
result, err := http.DefaultClient.Do(request)
if err != nil {
return 0, nil, err
}
defer result.Body.Close()
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return 0, nil, err
}
if result.StatusCode > 200 {
return 0, nil, kserr.Parse(data)
}
err = json.Unmarshal(data, &devOpsProjects)
if err != nil {
return 0, nil, err
}
if keyword != "" {
for i := 0; i < len(devOpsProjects); i++ {
if !strings.Contains(devOpsProjects[i].Name, keyword) {
devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...)
i--
}
}
}
sort.Slice(devOpsProjects, func(i, j int) bool {
switch orderBy {
case "name":
if reverse {
return devOpsProjects[i].Name < devOpsProjects[j].Name
} else {
return devOpsProjects[i].Name > devOpsProjects[j].Name
}
default:
if reverse {
return devOpsProjects[i].CreateTime.After(*devOpsProjects[j].CreateTime)
} else {
return devOpsProjects[i].CreateTime.Before(*devOpsProjects[j].CreateTime)
}
}
})
for i := 0; i < len(devOpsProjects); i++ {
inWorkspace := false
for _, binding := range workspaceDOPBindings {
if binding.DevOpsProject == *devOpsProjects[i].ProjectId {
inWorkspace = true
}
}
if !inWorkspace {
devOpsProjects = append(devOpsProjects[:i], devOpsProjects[i+1:]...)
i--
}
}
if len(devOpsProjects) < offset {
return len(devOpsProjects), make([]models.DevopsProject, 0), nil
} else if len(devOpsProjects) < limit+offset {
return len(devOpsProjects), devOpsProjects[offset:], nil
} else {
return len(devOpsProjects), devOpsProjects[offset : limit+offset], nil
}
}
func convertGroupToWorkspace(db *gorm.DB, group models.Group) (*models.Workspace, error) {
namespaces, err := Namespaces(group.Name)
@@ -642,449 +436,73 @@ func convertGroupToWorkspace(db *gorm.DB, group models.Group) (*models.Workspace
return &workspace, nil
}
func CreateNamespace(namespace *core.Namespace) (*core.Namespace, error) {
func InviteUser(workspaceName string, user *models.User) error {
ns, err := k8s.Client().CoreV1().Namespaces().Create(namespace)
workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, user.Username)
if err != nil {
return nil, err
}
return ns, nil
}
func Invite(workspaceName string, users []models.UserInvite) error {
for _, user := range users {
if !slice.ContainsString(constants.WorkSpaceRoles, user.Role, nil) {
return fmt.Errorf("role %s not exist", user.Role)
}
}
workspace, err := Detail(workspaceName)
if err != nil {
if err != nil && !apierrors.IsNotFound(err) {
return err
}
for _, user := range users {
if !slice.ContainsString(workspace.Members, user.Username, nil) {
workspace.Members = append(workspace.Members, user.Username)
}
}
workspaceRoleName := fmt.Sprintf("workspace:%s:%s", workspaceName, strings.TrimPrefix(user.WorkspaceRole, "workspace-"))
workspace, err = Edit(workspace)
if err != nil {
return err
}
for _, user := range users {
err := CreateWorkspaceRoleBinding(workspace, user.Username, user.Role)
if workspaceRole != nil && workspaceRole.Name != workspaceRoleName {
err := DeleteWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole)
if err != nil {
return err
}
}
return nil
return CreateWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole)
}
func NamespaceExistCheck(namespaceName string) (bool, error) {
func CreateWorkspaceRoleBinding(workspace, username string, role string) error {
_, err := k8s.Client().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return false, nil
} else {
return false, err
}
if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
return apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
}
return true, nil
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
}
if !k8sutil.ContainsUser(workspaceRoleBinding.Subjects, username) {
workspaceRoleBinding.Subjects = append(workspaceRoleBinding.Subjects, v1.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: username})
_, err = k8s.Client().RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding)
}
return err
}
func RemoveMembers(workspaceName string, users []string) error {
func DeleteWorkspaceRoleBinding(workspace, username string, role string) error {
workspace, err := Detail(workspaceName)
if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
return apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
}
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
}
err = UnbindWorkspace(workspace, users)
if err != nil {
return err
}
for i := 0; i < len(workspace.Members); i++ {
if slice.ContainsString(users, workspace.Members[i], nil) {
workspace.Members = append(workspace.Members[:i], workspace.Members[i+1:]...)
for i, v := range workspaceRoleBinding.Subjects {
if v.Kind == v1.UserKind && v.Name == username {
workspaceRoleBinding.Subjects = append(workspaceRoleBinding.Subjects[:i], workspaceRoleBinding.Subjects[i+1:]...)
i--
}
}
workspace, err = Edit(workspace)
workspaceRoleBinding, err = k8s.Client().RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding)
if err != nil {
return err
}
return nil
}
func Roles(workspace *models.Workspace) ([]*v1.ClusterRole, error) {
roles := make([]*v1.ClusterRole, 0)
clusterRoleLister := informers.SharedInformerFactory().Rbac().V1().ClusterRoles().Lister()
for _, name := range constants.WorkSpaceRoles {
clusterRole, err := clusterRoleLister.Get(fmt.Sprintf("system:%s:%s", workspace.Name, name))
if err != nil {
if apierrors.IsNotFound(err) {
go WorkspaceRoleInit(workspace)
}
return nil, err
}
clusterRole = clusterRole.DeepCopy()
clusterRole.Name = name
roles = append(roles, clusterRole)
}
return roles, nil
}
func WorkspaceRoleInit(workspace *models.Workspace) error {
k8sClient := k8s.Client()
admin := new(v1.ClusterRole)
admin.Name = fmt.Sprintf("system:%s:%s", workspace.Name, constants.WorkspaceAdmin)
admin.Kind = iam.ClusterRoleKind
admin.Rules = []v1.PolicyRule{
{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io", "account.kubesphere.io"},
ResourceNames: []string{workspace.Name},
Resources: []string{"workspaces", "workspaces/*"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"devops.kubesphere.io", "jenkins.kubesphere.io"},
Resources: []string{"*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"namespaces"},
Resources: []string{"status/*", "monitoring/*", "quota/*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"resources"},
},
{
Verbs: []string{"list"},
APIGroups: []string{"account.kubesphere.io"},
Resources: []string{"users"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"workspaces"},
Resources: []string{"monitoring/" + workspace.Name},
},
}
admin.Labels = map[string]string{"creator": "system"}
regular := new(v1.ClusterRole)
regular.Name = fmt.Sprintf("system:%s:%s", workspace.Name, constants.WorkspaceRegular)
regular.Kind = iam.ClusterRoleKind
regular.Rules = []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces"},
ResourceNames: []string{workspace.Name},
}, {
Verbs: []string{"create"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces", "workspaces/devops"},
ResourceNames: []string{workspace.Name},
},
{
Verbs: []string{"delete"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces", "workspaces/devops"},
ResourceNames: []string{workspace.Name},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"namespaces"},
Resources: []string{"quota/*", "status/*", "monitoring/*"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"devops.kubesphere.io"},
Resources: []string{"*"},
}, {
Verbs: []string{"*"},
APIGroups: []string{"jenkins.kubesphere.io"},
Resources: []string{"*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"resources"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{workspace.Name},
Resources: []string{"workspaces/members"},
},
}
regular.Labels = map[string]string{"creator": "system"}
viewer := new(v1.ClusterRole)
viewer.Name = fmt.Sprintf("system:%s:%s", workspace.Name, constants.WorkspaceViewer)
viewer.Kind = iam.ClusterRoleKind
viewer.Rules = []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io", "account.kubesphere.io"},
ResourceNames: []string{workspace.Name},
Resources: []string{"workspaces", "workspaces/*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"namespaces"},
Resources: []string{"quota/*", "status/*", "monitoring/*"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"resources"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{"workspaces"},
Resources: []string{"monitoring/" + workspace.Name},
},
{
Verbs: []string{"get", "list"},
APIGroups: []string{"devops.kubesphere.io"},
Resources: []string{"*"},
}, {
Verbs: []string{"get", "list"},
APIGroups: []string{"jenkins.kubesphere.io"},
Resources: []string{"*"},
},
}
viewer.Labels = map[string]string{"creator": "system"}
_, err := k8sClient.RbacV1().ClusterRoles().Create(admin)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster role create failed", admin.Name, err)
return err
}
}
adminRoleBinding := new(v1.ClusterRoleBinding)
adminRoleBinding.Name = admin.Name
adminRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: admin.Name}
adminRoleBinding.Subjects = []v1.Subject{{Kind: v1.UserKind, Name: workspace.Creator}}
_, err = k8sClient.RbacV1().ClusterRoleBindings().Create(adminRoleBinding)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster rolebinding create failed", adminRoleBinding.Name, err)
return err
}
}
_, err = k8sClient.RbacV1().ClusterRoles().Create(regular)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster role create failed", viewer.Name, err)
return err
}
}
regularRoleBinding := new(v1.ClusterRoleBinding)
regularRoleBinding.Name = regular.Name
regularRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: regular.Name}
regularRoleBinding.Subjects = make([]v1.Subject, 0)
_, err = k8sClient.RbacV1().ClusterRoleBindings().Create(regularRoleBinding)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster rolebinding create failed", regularRoleBinding.Name, err)
return err
}
}
_, err = k8sClient.RbacV1().ClusterRoles().Create(viewer)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster role create failed", viewer.Name, err)
return err
}
}
viewerRoleBinding := new(v1.ClusterRoleBinding)
viewerRoleBinding.Name = viewer.Name
viewerRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: viewer.Name}
viewerRoleBinding.Subjects = make([]v1.Subject, 0)
_, err = k8sClient.RbacV1().ClusterRoleBindings().Create(viewerRoleBinding)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster rolebinding create failed", viewerRoleBinding.Name, err)
return err
}
}
return nil
}
func unbindWorkspaceRole(workspace string, users []string) error {
k8sClient := k8s.Client()
for _, name := range constants.WorkSpaceRoles {
roleBinding, err := k8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:%s", workspace, name), meta_v1.GetOptions{})
if err != nil {
return err
}
modify := false
for i := 0; i < len(roleBinding.Subjects); i++ {
if roleBinding.Subjects[i].Kind == v1.UserKind && slice.ContainsString(users, roleBinding.Subjects[i].Name, nil) {
roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...)
i--
modify = true
}
}
if modify {
roleBinding, err = k8sClient.RbacV1().ClusterRoleBindings().Update(roleBinding)
if err != nil {
return err
}
}
}
return nil
}
func unbindNamespacesRole(namespaces []string, users []string) error {
k8sClient := k8s.Client()
for _, namespace := range namespaces {
roleBindings, err := k8sClient.RbacV1().RoleBindings(namespace).List(meta_v1.ListOptions{})
if err != nil {
return err
}
for _, roleBinding := range roleBindings.Items {
modify := false
for i := 0; i < len(roleBinding.Subjects); i++ {
if roleBinding.Subjects[i].Kind == v1.UserKind && slice.ContainsString(users, roleBinding.Subjects[i].Name, nil) {
roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...)
modify = true
}
}
if modify {
_, err := k8sClient.RbacV1().RoleBindings(namespace).Update(&roleBinding)
if err != nil {
return err
}
}
}
}
return nil
}
func UnbindWorkspace(workspace *models.Workspace, users []string) error {
err := unbindNamespacesRole(workspace.Namespaces, users)
if err != nil {
return err
}
err = unbindWorkspaceRole(workspace.Name, users)
if err != nil {
return err
}
return nil
}
func CreateWorkspaceRoleBinding(workspace *models.Workspace, username string, role string) error {
k8sClient := k8s.Client()
for _, roleName := range constants.WorkSpaceRoles {
roleBinding, err := k8sClient.RbacV1().ClusterRoleBindings().Get(fmt.Sprintf("system:%s:%s", workspace.Name, roleName), meta_v1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
go WorkspaceRoleInit(workspace)
}
return err
}
modify := false
for i, v := range roleBinding.Subjects {
if v.Kind == v1.UserKind && v.Name == username {
if roleName == role {
return nil
} else {
modify = true
roleBinding.Subjects = append(roleBinding.Subjects[:i], roleBinding.Subjects[i+1:]...)
if roleName == constants.WorkspaceAdmin || roleName == constants.WorkspaceViewer {
go deleteDevopsRoleBinding(workspace.Name, "", username)
}
break
}
}
}
if roleName == role {
modify = true
roleBinding.Subjects = append(roleBinding.Subjects, v1.Subject{Kind: v1.UserKind, Name: username})
if roleName == constants.WorkspaceAdmin {
go createDevopsRoleBinding(workspace.Name, "", username, constants.DevopsOwner)
} else if roleName == constants.WorkspaceViewer {
go createDevopsRoleBinding(workspace.Name, "", username, constants.DevopsReporter)
}
}
if !modify {
continue
}
_, err = k8sClient.RbacV1().ClusterRoleBindings().Update(roleBinding)
if err != nil {
return err
}
}
return nil
return err
}
func GetDevOpsProjects(workspaceName string) ([]string, error) {
@@ -1105,12 +523,12 @@ func GetDevOpsProjects(workspaceName string) ([]string, error) {
return devOpsProjects, nil
}
func GetOrgMembers(workspace string) ([]string, error) {
ws, err := Detail(workspace)
func WorkspaceUserCount(workspace string) (int, error) {
count, err := iam.WorkspaceUsersTotalCount(workspace)
if err != nil {
return nil, err
return 0, err
}
return ws.Members, nil
return count, nil
}
func GetOrgRoles(name string) ([]string, error) {
@@ -1135,13 +553,13 @@ func WorkspaceNamespaces(workspaceName string) ([]string, error) {
func WorkspaceCount() (int, error) {
workspaces, err := iam.ChildList("")
ws, err := resources.ListResources("", resources.Workspaces, &params.Conditions{}, "", false, 1, 0)
if err != nil {
return 0, err
}
return len(workspaces), nil
return ws.TotalCount, nil
}
func GetAllProjectNums() (int, error) {
@@ -1155,7 +573,6 @@ func GetAllProjectNums() (int, error) {
func GetAllDevOpsProjectsNums() (int, error) {
db := mysql.Client()
var count int
if err := db.Model(&models.WorkspaceDPBinding{}).Count(&count).Error; err != nil {
return 0, err
@@ -1164,11 +581,9 @@ func GetAllDevOpsProjectsNums() (int, error) {
}
func GetAllAccountNums() (int, error) {
totalCount, _, err := iam.UserList(1, 0)
users, err := iam.ListUsers(&params.Conditions{}, "", false, 1, 0)
if err != nil {
return 0, err
}
return totalCount, nil
return users.TotalCount, nil
}