590 lines
15 KiB
Go
590 lines
15 KiB
Go
/*
|
|
|
|
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 workspaces
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
|
|
"kubesphere.io/kubesphere/pkg/models/resources"
|
|
"kubesphere.io/kubesphere/pkg/params"
|
|
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
|
|
"kubesphere.io/kubesphere/pkg/simple/client/ldap"
|
|
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
|
|
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
|
|
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
|
"net/http"
|
|
|
|
"kubesphere.io/kubesphere/pkg/constants"
|
|
"kubesphere.io/kubesphere/pkg/informers"
|
|
"kubesphere.io/kubesphere/pkg/models"
|
|
"kubesphere.io/kubesphere/pkg/models/iam"
|
|
|
|
"strings"
|
|
|
|
"github.com/jinzhu/gorm"
|
|
core "k8s.io/api/core/v1"
|
|
|
|
"errors"
|
|
"github.com/golang/glog"
|
|
"k8s.io/api/rbac/v1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
|
|
"sort"
|
|
|
|
kserr "kubesphere.io/kubesphere/pkg/errors"
|
|
)
|
|
|
|
func UnBindDevopsProject(workspace string, devops string) error {
|
|
db := mysql.Client()
|
|
return db.Delete(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error
|
|
}
|
|
|
|
func DeleteDevopsProject(username string, devops string) error {
|
|
request, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, devops), nil)
|
|
request.Header.Add("X-Token-Username", username)
|
|
|
|
result, err := http.DefaultClient.Do(request)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer result.Body.Close()
|
|
data, err := ioutil.ReadAll(result.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result.StatusCode > 200 {
|
|
return kserr.Parse(data)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func CreateDevopsProject(username string, workspace string, devops models.DevopsProject) (*models.DevopsProject, error) {
|
|
|
|
data, err := json.Marshal(devops)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/api/v1alpha/projects", constants.DevopsAPIServer), bytes.NewReader(data))
|
|
request.Header.Add("X-Token-Username", username)
|
|
request.Header.Add("Content-Type", "application/json")
|
|
result, err := http.DefaultClient.Do(request)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer result.Body.Close()
|
|
data, err = ioutil.ReadAll(result.Body)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.StatusCode > 200 {
|
|
return nil, kserr.Parse(data)
|
|
}
|
|
|
|
var project models.DevopsProject
|
|
|
|
err = json.Unmarshal(data, &project)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = BindingDevopsProject(workspace, *project.ProjectId)
|
|
|
|
if err != nil {
|
|
DeleteDevopsProject(username, *project.ProjectId)
|
|
return nil, err
|
|
}
|
|
|
|
go createDefaultDevopsRoleBinding(workspace, project)
|
|
|
|
return &project, nil
|
|
}
|
|
|
|
func createDefaultDevopsRoleBinding(workspace string, project models.DevopsProject) error {
|
|
admins := []string{""}
|
|
|
|
for _, admin := range admins {
|
|
createDevopsRoleBinding(workspace, *project.ProjectId, admin, constants.DevopsOwner)
|
|
}
|
|
|
|
viewers := []string{""}
|
|
|
|
for _, viewer := range viewers {
|
|
createDevopsRoleBinding(workspace, *project.ProjectId, viewer, constants.DevopsReporter)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createDevopsRoleBinding(workspace string, projectId string, user string, role string) {
|
|
|
|
projects := make([]string, 0)
|
|
|
|
if projectId != "" {
|
|
projects = append(projects, projectId)
|
|
} else {
|
|
p, err := GetDevOpsProjects(workspace)
|
|
if err != nil {
|
|
glog.Warning("create devops role binding failed", workspace, projectId, user, role)
|
|
return
|
|
}
|
|
projects = append(projects, p...)
|
|
}
|
|
|
|
for _, project := range projects {
|
|
data := []byte(fmt.Sprintf(`{"username":"%s","role":"%s"}`, user, role))
|
|
request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("http://%s/api/v1alpha/projects/%s/members", constants.DevopsAPIServer, project), bytes.NewReader(data))
|
|
request.Header.Add("Content-Type", "application/json")
|
|
request.Header.Add("X-Token-Username", "admin")
|
|
resp, err := http.DefaultClient.Do(request)
|
|
if err != nil || resp.StatusCode > 200 {
|
|
glog.Warning(fmt.Sprintf("create devops role binding failed %s,%s,%s,%s", workspace, project, user, role))
|
|
}
|
|
if resp != nil {
|
|
resp.Body.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
func ListNamespaceByUser(workspaceName string, username string, keyword string, orderBy string, reverse bool, limit int, offset int) (int, []*core.Namespace, error) {
|
|
|
|
namespaces, err := Namespaces(workspaceName)
|
|
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
if keyword != "" {
|
|
for i := 0; i < len(namespaces); i++ {
|
|
if !strings.Contains(namespaces[i].Name, keyword) {
|
|
namespaces = append(namespaces[:i], namespaces[i+1:]...)
|
|
i--
|
|
}
|
|
}
|
|
}
|
|
|
|
sort.Slice(namespaces, func(i, j int) bool {
|
|
switch orderBy {
|
|
case "name":
|
|
if reverse {
|
|
return namespaces[i].Name < namespaces[j].Name
|
|
} else {
|
|
return namespaces[i].Name > namespaces[j].Name
|
|
}
|
|
default:
|
|
if reverse {
|
|
return namespaces[i].CreationTimestamp.Time.After(namespaces[j].CreationTimestamp.Time)
|
|
} else {
|
|
return namespaces[i].CreationTimestamp.Time.Before(namespaces[j].CreationTimestamp.Time)
|
|
}
|
|
}
|
|
})
|
|
|
|
rules, err := iam.GetUserClusterRules(username)
|
|
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
namespacesManager := v1.PolicyRule{APIGroups: []string{"kubesphere.io"}, ResourceNames: []string{workspaceName}, Verbs: []string{"get"}, Resources: []string{"workspaces/namespaces"}}
|
|
|
|
if !iam.RulesMatchesRequired(rules, namespacesManager) {
|
|
for i := 0; i < len(namespaces); i++ {
|
|
roles, err := iam.GetUserRoles(namespaces[i].Name, username)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
rules := make([]v1.PolicyRule, 0)
|
|
for _, role := range roles {
|
|
rules = append(rules, role.Rules...)
|
|
}
|
|
if !iam.RulesMatchesRequired(rules, v1.PolicyRule{APIGroups: []string{""}, ResourceNames: []string{namespaces[i].Name}, Verbs: []string{"get"}, Resources: []string{"namespaces"}}) {
|
|
namespaces = append(namespaces[:i], namespaces[i+1:]...)
|
|
i--
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(namespaces) < offset {
|
|
return len(namespaces), namespaces, nil
|
|
} else if len(namespaces) < limit+offset {
|
|
return len(namespaces), namespaces[offset:], nil
|
|
} else {
|
|
return len(namespaces), namespaces[offset : limit+offset], nil
|
|
}
|
|
}
|
|
|
|
func Namespaces(workspaceName string) ([]*core.Namespace, error) {
|
|
namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
|
|
namespaces, err := namespaceLister.List(labels.SelectorFromSet(labels.Set{"kubesphere.io/workspace": workspaceName}))
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if namespaces == nil {
|
|
return make([]*core.Namespace, 0), nil
|
|
}
|
|
|
|
out := make([]*core.Namespace, len(namespaces))
|
|
|
|
for i, v := range namespaces {
|
|
out[i] = v.DeepCopy()
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func BindingDevopsProject(workspace string, devops string) error {
|
|
db := mysql.Client()
|
|
return db.Create(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error
|
|
}
|
|
|
|
func DeleteNamespace(workspace string, namespaceName string) error {
|
|
namespace, err := k8s.Client().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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 {
|
|
|
|
err := release(workspace)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = iam.DeleteGroup(workspace.Name)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO
|
|
func release(workspace *models.Workspace) error {
|
|
for _, namespace := range workspace.Namespaces {
|
|
err := DeleteNamespace(workspace.Name, namespace)
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, devops := range workspace.DevopsProjects {
|
|
err := DeleteDevopsProject("admin", devops)
|
|
if err != nil && !strings.Contains(err.Error(), "not found") {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := workspaceRoleRelease(workspace.Name)
|
|
|
|
return err
|
|
}
|
|
func workspaceRoleRelease(workspace string) error {
|
|
k8sClient := k8s.Client()
|
|
deletePolicy := meta_v1.DeletePropagationForeground
|
|
|
|
for _, role := range constants.WorkSpaceRoles {
|
|
err := k8sClient.RbacV1().ClusterRoles().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy})
|
|
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, role := range constants.WorkSpaceRoles {
|
|
err := k8sClient.RbacV1().ClusterRoleBindings().Delete(fmt.Sprintf("system:%s:%s", workspace, role), &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy})
|
|
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Edit(workspace *models.Workspace) (*models.Workspace, error) {
|
|
|
|
group, err := iam.UpdateGroup(&workspace.Group)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
workspace.Group = *group
|
|
|
|
return workspace, nil
|
|
}
|
|
|
|
func DescribeWorkspace(workspaceName string) (*v1alpha1.Workspace, error) {
|
|
workspace, err := informers.KsSharedInformerFactory().Tenant().V1alpha1().Workspaces().Lister().Get(workspaceName)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return workspace, nil
|
|
}
|
|
|
|
func fetch(names []string) ([]*models.Workspace, error) {
|
|
|
|
if names != nil && len(names) == 0 {
|
|
return make([]*models.Workspace, 0), nil
|
|
}
|
|
var groups []models.Group
|
|
var err error
|
|
if names == nil {
|
|
groups, err = iam.ChildList("")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
conn, err := ldap.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer conn.Close()
|
|
for _, name := range names {
|
|
group, err := iam.DescribeGroup(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groups = append(groups, *group)
|
|
}
|
|
}
|
|
|
|
db := mysql.Client()
|
|
|
|
workspaces := make([]*models.Workspace, 0)
|
|
for _, group := range groups {
|
|
workspace, err := convertGroupToWorkspace(db, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
workspaces = append(workspaces, workspace)
|
|
}
|
|
|
|
return workspaces, nil
|
|
}
|
|
|
|
func convertGroupToWorkspace(db *gorm.DB, group models.Group) (*models.Workspace, error) {
|
|
namespaces, err := Namespaces(group.Name)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
namespacesNames := make([]string, 0)
|
|
|
|
for _, namespace := range namespaces {
|
|
namespacesNames = append(namespacesNames, namespace.Name)
|
|
}
|
|
|
|
var workspaceDOPBindings []models.WorkspaceDPBinding
|
|
|
|
if err := db.Where("workspace = ?", group.Name).Find(&workspaceDOPBindings).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devOpsProjects := make([]string, 0)
|
|
|
|
for _, workspaceDOPBinding := range workspaceDOPBindings {
|
|
devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject)
|
|
}
|
|
|
|
workspace := models.Workspace{Group: group}
|
|
workspace.Namespaces = namespacesNames
|
|
workspace.DevopsProjects = devOpsProjects
|
|
return &workspace, nil
|
|
}
|
|
|
|
func InviteUser(workspaceName string, user *models.User) error {
|
|
|
|
workspaceRole, err := iam.GetUserWorkspaceRole(workspaceName, user.Username)
|
|
|
|
if err != nil && !apierrors.IsNotFound(err) {
|
|
return err
|
|
}
|
|
|
|
workspaceRoleName := fmt.Sprintf("workspace:%s:%s", workspaceName, strings.TrimPrefix(user.WorkspaceRole, "workspace-"))
|
|
|
|
if workspaceRole != nil && workspaceRole.Name != workspaceRoleName {
|
|
err := DeleteWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return CreateWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole)
|
|
}
|
|
|
|
func CreateWorkspaceRoleBinding(workspace, username string, role string) error {
|
|
|
|
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
|
|
}
|
|
|
|
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 DeleteWorkspaceRoleBinding(workspace, username string, role string) error {
|
|
|
|
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
|
|
}
|
|
|
|
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--
|
|
}
|
|
}
|
|
|
|
workspaceRoleBinding, err = k8s.Client().RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding)
|
|
|
|
return err
|
|
}
|
|
|
|
func GetDevOpsProjects(workspaceName string) ([]string, error) {
|
|
|
|
db := mysql.Client()
|
|
|
|
var workspaceDOPBindings []models.WorkspaceDPBinding
|
|
|
|
if err := db.Where("workspace = ?", workspaceName).Find(&workspaceDOPBindings).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devOpsProjects := make([]string, 0)
|
|
|
|
for _, workspaceDOPBinding := range workspaceDOPBindings {
|
|
devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject)
|
|
}
|
|
return devOpsProjects, nil
|
|
}
|
|
|
|
func WorkspaceUserCount(workspace string) (int, error) {
|
|
count, err := iam.WorkspaceUsersTotalCount(workspace)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func GetOrgRoles(name string) ([]string, error) {
|
|
return constants.WorkSpaceRoles, nil
|
|
}
|
|
|
|
func WorkspaceNamespaces(workspaceName string) ([]string, error) {
|
|
ns, err := Namespaces(workspaceName)
|
|
|
|
namespaces := make([]string, 0)
|
|
|
|
if err != nil {
|
|
return namespaces, err
|
|
}
|
|
|
|
for i := 0; i < len(ns); i++ {
|
|
namespaces = append(namespaces, ns[i].Name)
|
|
}
|
|
|
|
return namespaces, nil
|
|
}
|
|
|
|
func WorkspaceCount() (int, error) {
|
|
|
|
ws, err := resources.ListResources("", resources.Workspaces, ¶ms.Conditions{}, "", false, 1, 0)
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return ws.TotalCount, nil
|
|
}
|
|
|
|
func GetAllProjectNums() (int, error) {
|
|
namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
|
|
list, err := namespaceLister.List(labels.Everything())
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(list), nil
|
|
}
|
|
|
|
func GetAllDevOpsProjectsNums() (int, error) {
|
|
db := mysql.Client()
|
|
var count int
|
|
if err := db.Model(&models.WorkspaceDPBinding{}).Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
func GetAllAccountNums() (int, error) {
|
|
users, err := iam.ListUsers(¶ms.Conditions{}, "", false, 1, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return users.TotalCount, nil
|
|
}
|