Files
kubesphere/pkg/models/workspaces/workspaces.go
2018-10-22 17:18:20 +08:00

860 lines
20 KiB
Go

package workspaces
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/jinzhu/gorm"
core "k8s.io/api/core/v1"
k8sErr "k8s.io/apimachinery/pkg/api/errors"
"log"
"strings"
"github.com/emicklei/go-restful"
"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"
v13 "k8s.io/client-go/listers/rbac/v1"
"k8s.io/kubernetes/pkg/util/slice"
"kubesphere.io/kubesphere/pkg/client"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/controllers"
"kubesphere.io/kubesphere/pkg/models/iam"
ksErr "kubesphere.io/kubesphere/pkg/util/errors"
)
var WorkSpaceRoles = []string{"admin", "operator", "viewer"}
func UnBindNamespace(workspace string, namespace string) error {
db := client.NewSharedDBClient()
defer db.Close()
return db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error
}
func UnBindDevopsProject(workspace string, devops string) error {
db := client.NewSharedDBClient()
defer db.Close()
return db.Delete(&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
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return err
}
if result.StatusCode > 200 {
return ksErr.Wrap(data)
}
return nil
}
func CreateDevopsProject(username string, devops DevopsProject) (*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
}
data, err = ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var project DevopsProject
err = json.Unmarshal(data, &project)
if err != nil {
return nil, err
}
return &project, nil
}
func Namespaces(workspace string) ([]*core.Namespace, error) {
db := client.NewSharedDBClient()
defer db.Close()
var workspaceNSBindings []WorkspaceNSBinding
if err := db.Where("workspace = ?", workspace).Find(&workspaceNSBindings).Error; err != nil {
return nil, err
}
namespaces := make([]*core.Namespace, 0)
for _, workspaceNSBinding := range workspaceNSBindings {
namespace, err := client.NewK8sClient().CoreV1().Namespaces().Get(workspaceNSBinding.Namespace, meta_v1.GetOptions{})
if err != nil {
if k8sErr.IsNotFound(err) {
db.Delete(&WorkspaceNSBinding{Workspace: workspace, Namespace: workspaceNSBinding.Namespace})
} else {
return nil, err
}
} else {
namespaces = append(namespaces, namespace)
}
}
return namespaces, nil
}
func BindingDevopsProject(workspace string, devops string) error {
db := client.NewSharedDBClient()
defer db.Close()
return db.Create(&WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error
}
func DeleteNamespace(namespace string) error {
deletePolicy := meta_v1.DeletePropagationBackground
err := client.NewK8sClient().CoreV1().Namespaces().Delete(namespace, &meta_v1.DeleteOptions{PropagationPolicy: &deletePolicy})
return err
}
func Delete(workspace *Workspace) error {
err := release(workspace)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, workspace.Name), nil)
if err != nil {
return err
}
result, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return err
}
if result.StatusCode > 200 {
return ksErr.Wrap(data)
}
return nil
}
func release(workspace *Workspace) error {
for _, namespace := range workspace.Namespaces {
err := DeleteNamespace(namespace)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
}
for _, devops := range workspace.DevopsProjects {
err := DeleteDevopsProject(workspace.Creator, devops)
if err != nil {
return err
}
}
err := workspaceRoleRelease(workspace.Name)
return err
}
func workspaceRoleRelease(workspace string) error {
k8sClient := client.NewK8sClient()
deletePolicy := meta_v1.DeletePropagationForeground
for _, role := range 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 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 Create(workspace *Workspace) (*Workspace, error) {
data, err := json.Marshal(workspace)
if err != nil {
return nil, err
}
result, err := http.Post(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer), restful.MIME_JSON, bytes.NewReader(data))
if err != nil {
return nil, err
}
data, err = ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var created Workspace
err = json.Unmarshal(data, &created)
if err != nil {
return nil, err
}
created.Members = make([]string, 0)
created.Namespaces = make([]string, 0)
created.DevopsProjects = make([]string, 0)
go WorkspaceRoleInit(workspace)
return &created, nil
}
func Edit(workspace *Workspace) (*Workspace, error) {
data, err := json.Marshal(workspace)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, workspace.Name), bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
if err != nil {
return nil, err
}
result, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
data, err = ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var edited Workspace
err = json.Unmarshal(data, &edited)
if err != nil {
return nil, err
}
return &edited, nil
}
func Detail(name string) (*Workspace, error) {
result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s", constants.AccountAPIServer, name))
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var group Group
err = json.Unmarshal(data, &group)
if err != nil {
return nil, err
}
db := client.NewSharedDBClient()
defer db.Close()
workspace, err := convertGroupToWorkspace(db, group)
if err != nil {
return nil, err
}
return workspace, nil
}
func List(names []string) ([]*Workspace, error) {
url := fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups", constants.AccountAPIServer)
if names != nil {
url = url + "?path=" + strings.Join(names, ",")
}
result, err := http.Get(url)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var groups []Group
err = json.Unmarshal(data, &groups)
if err != nil {
return nil, err
}
db := client.NewSharedDBClient()
defer db.Close()
workspaces := make([]*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 DevopsProjects(workspace string) ([]DevopsProject, error) {
db := client.NewSharedDBClient()
defer db.Close()
var workspaceDOPBindings []WorkspaceDPBinding
if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil {
return nil, err
}
devOpsProjects := make([]DevopsProject, 0)
for _, workspaceDOPBinding := range workspaceDOPBindings {
request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/api/v1alpha/projects/%s", constants.DevopsAPIServer, workspaceDOPBinding.DevOpsProject), nil)
request.Header.Add("X-Token-Username", "admin")
result, err := http.DefaultClient.Do(request)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode == 403 || result.StatusCode == 404 {
if err := db.Delete(&workspaceDOPBinding).Error; err != nil {
return nil, err
}
continue
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var project DevopsProject
err = json.Unmarshal(data, &project)
if err != nil {
return nil, err
}
devOpsProjects = append(devOpsProjects, project)
}
return devOpsProjects, nil
}
func convertGroupToWorkspace(db *gorm.DB, group Group) (*Workspace, error) {
var workspaceNSBindings []WorkspaceNSBinding
if err := db.Where("workspace = ?", group.Name).Find(&workspaceNSBindings).Error; err != nil {
return nil, err
}
namespaces := make([]string, 0)
for _, workspaceNSBinding := range workspaceNSBindings {
namespaces = append(namespaces, workspaceNSBinding.Namespace)
}
var workspaceDOPBindings []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 := Workspace{Group: group}
workspace.Namespaces = namespaces
workspace.DevopsProjects = devOpsProjects
return &workspace, nil
}
func CreateNamespace(namespace *core.Namespace) (*core.Namespace, error) {
return client.NewK8sClient().CoreV1().Namespaces().Create(namespace)
}
func BindingNamespace(workspace string, namespace string) error {
db := client.NewSharedDBClient()
defer db.Close()
return db.Create(&WorkspaceNSBinding{Workspace: workspace, Namespace: namespace}).Error
}
func Invite(workspaceName string, users []UserInvite) error {
for _, user := range users {
if !slice.ContainsString(WorkSpaceRoles, user.Role, nil) {
return fmt.Errorf("role %s not exist", user.Role)
}
}
workspace, err := Detail(workspaceName)
if err != nil {
return err
}
for _, user := range users {
if !slice.ContainsString(workspace.Members, user.Username, nil) {
workspace.Members = append(workspace.Members, user.Username)
}
}
workspace, err = Edit(workspace)
if err != nil {
return err
}
for _, user := range users {
err := CreateWorkspaceRoleBinding(workspace, user.Username, user.Role)
if err != nil {
return err
}
}
return nil
}
func RemoveMembers(workspaceName string, users []string) error {
workspace, err := Detail(workspaceName)
if err != nil {
return err
}
err = UnbindWorkspace(workspace, users)
if err != nil {
return err
}
return nil
}
//func checkUserExist(username string) (bool, error) {
// result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/users?check=%s", constants.AccountAPIServer, username))
//
// if err != nil {
// return false, err
// }
//
// data, err := ioutil.ReadAll(result.Body)
//
// if err != nil {
// return false, err
// }
//
// if result.StatusCode > 200 {
// return false, ksErr.Wrap(data)
// }
//
// var r map[string]bool
//
// err = json.Unmarshal(data, &r)
//
// if err != nil {
// return false, err
// }
//
// return r["exist"], nil
//
//}
func Roles(workspace *Workspace) ([]*v1.ClusterRole, error) {
roles := make([]*v1.ClusterRole, 0)
k8sClient := client.NewK8sClient()
for _, name := range WorkSpaceRoles {
role, err := k8sClient.RbacV1().ClusterRoles().Get(fmt.Sprintf("system:%s:%s", workspace.Name, name), meta_v1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
go WorkspaceRoleInit(workspace)
}
return nil, err
}
role.Name = name
roles = append(roles, role)
}
return roles, nil
}
func GetWorkspaceMembers(workspace string) ([]iam.User, error) {
result, err := http.Get(fmt.Sprintf("http://%s/apis/account.kubesphere.io/v1alpha1/groups/%s/users", constants.AccountAPIServer, workspace))
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(result.Body)
if err != nil {
return nil, err
}
if result.StatusCode > 200 {
return nil, ksErr.Wrap(data)
}
var users []iam.User
err = json.Unmarshal(data, &users)
if err != nil {
return nil, err
}
return users, nil
}
func WorkspaceRoleInit(workspace *Workspace) error {
k8sClient := client.NewK8sClient()
admin := new(v1.ClusterRole)
admin.Name = fmt.Sprintf("system:%s:admin", workspace.Name)
admin.Kind = iam.ClusterRoleKind
admin.Rules = []v1.PolicyRule{
{
Verbs: []string{"*"},
APIGroups: []string{"kubesphere.io"},
ResourceNames: []string{workspace.Name},
Resources: []string{"workspaces", "workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"},
},
}
admin.Labels = map[string]string{"creator": "system"}
operator := new(v1.ClusterRole)
operator.Name = fmt.Sprintf("system:%s:operator", workspace.Name)
operator.Kind = iam.ClusterRoleKind
operator.Rules = []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces"},
ResourceNames: []string{workspace.Name},
}, {
Verbs: []string{"list", "create"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces", "workspaces/devops"},
ResourceNames: []string{workspace.Name},
}, {
Verbs: []string{"list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/members", "workspaces/registries"},
ResourceNames: []string{workspace.Name},
},
}
operator.Labels = map[string]string{"creator": "system"}
viewer := new(v1.ClusterRole)
viewer.Name = fmt.Sprintf("system:%s:viewer", workspace.Name)
viewer.Kind = iam.ClusterRoleKind
viewer.Rules = []v1.PolicyRule{
{
Verbs: []string{"get"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces"},
ResourceNames: []string{workspace.Name},
}, {
Verbs: []string{"list"},
APIGroups: []string{"kubesphere.io"},
Resources: []string{"workspaces/namespaces", "workspaces/members", "workspaces/devops", "workspaces/registries"},
ResourceNames: []string{workspace.Name},
},
}
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(operator)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster role create failed", viewer.Name, err)
return err
}
}
operatorRoleBinding := new(v1.ClusterRoleBinding)
operatorRoleBinding.Name = operator.Name
operatorRoleBinding.RoleRef = v1.RoleRef{Kind: "ClusterRole", Name: operator.Name}
operatorRoleBinding.Subjects = make([]v1.Subject, 0)
_, err = k8sClient.RbacV1().ClusterRoleBindings().Create(operatorRoleBinding)
if err != nil {
if !apierrors.IsAlreadyExists(err) {
log.Println("cluster rolebinding create failed", operatorRoleBinding.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 := client.NewK8sClient()
for _, name := range 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 {
lister := controllers.ResourceControllers.Controllers[controllers.Namespaces].Lister().(v13.RoleBindingLister)
k8sClient := client.NewK8sClient()
for _, namespace := range namespaces {
roleBindings, err := lister.RoleBindings(namespace).List(labels.Everything())
if err != nil {
return err
}
for _, roleBinding := range roleBindings {
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 *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 *Workspace, username string, role string) error {
k8sClient := client.NewK8sClient()
for _, roleName := range 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 err != nil {
return err
}
break
}
}
}
if roleName == role {
modify = true
roleBinding.Subjects = append(roleBinding.Subjects, v1.Subject{Kind: v1.UserKind, Name: username})
}
if !modify {
continue
}
_, err = k8sClient.RbacV1().ClusterRoleBindings().Update(roleBinding)
if err != nil {
return err
}
}
return nil
}