mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #4083 from tb365/feature/tb-user-batch-import
Feature/tb user batch import
This commit is contained in:
+204
-11
@@ -24,10 +24,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/360EntSecGroup-Skylar/excelize"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apigateway/options"
|
||||
"yunion.io/x/onecloud/pkg/appctx"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
@@ -36,6 +38,19 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||||
)
|
||||
|
||||
const (
|
||||
HOST_MAC = "*MAC地址"
|
||||
HOST_NAME = "*名称"
|
||||
HOST_IPMI_ADDR = "*IPMI地址"
|
||||
HOST_IPMI_USERNAME = "*IPMI用户名"
|
||||
HOST_IPMI_PASSWORD = "*IPMI密码"
|
||||
HOST_MNG_IP_ADDR = "*管理口IP地址"
|
||||
HOST_IPMI_ADDR_OPTIONAL = "IPMI地址"
|
||||
HOST_IPMI_USERNAME_OPTIONAL = "IPMI用户名"
|
||||
HOST_IPMI_PASSWORD_OPTIONAL = "IPMI密码"
|
||||
HOST_MNG_IP_ADDR_OPTIONAL = "管理口IP地址"
|
||||
)
|
||||
|
||||
func FetchSession(ctx context.Context, r *http.Request, apiVersion string) *mcclient.ClientSession {
|
||||
token := AppContextToken(ctx)
|
||||
session := auth.GetSession(ctx, token, FetchRegion(r), apiVersion)
|
||||
@@ -96,6 +111,10 @@ func (mh *MiscHandler) PostUploads(ctx context.Context, w http.ResponseWriter, r
|
||||
case "BatchHostRegister":
|
||||
mh.DoBatchHostRegister(ctx, w, req)
|
||||
return
|
||||
// 用户批量注册
|
||||
case "BatchUserRegister":
|
||||
mh.DoBatchUserRegister(ctx, w, req)
|
||||
return
|
||||
default:
|
||||
err := httperrors.NewInputParameterError("Unsupported action %s", actions[0])
|
||||
httperrors.JsonClientError(w, err)
|
||||
@@ -135,22 +154,48 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
|
||||
log.Errorf(err.Error())
|
||||
e := httperrors.NewInternalServerError("can't parse file")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
rows := xlsx.GetRows("hosts")
|
||||
if len(rows) == 0 {
|
||||
e := httperrors.NewGeneralError(fmt.Errorf("empty file content"))
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
h := ""
|
||||
paramKeys := []string{}
|
||||
for _, title := range rows[0] {
|
||||
switch title {
|
||||
case HOST_MAC:
|
||||
paramKeys = append(paramKeys, "access_mac")
|
||||
case HOST_NAME:
|
||||
paramKeys = append(paramKeys, "name")
|
||||
case HOST_IPMI_ADDR, HOST_IPMI_ADDR_OPTIONAL:
|
||||
paramKeys = append(paramKeys, "ipmi_ip_addr")
|
||||
case HOST_IPMI_USERNAME, HOST_IPMI_USERNAME_OPTIONAL:
|
||||
paramKeys = append(paramKeys, "ipmi_username")
|
||||
case HOST_IPMI_PASSWORD, HOST_IPMI_PASSWORD_OPTIONAL:
|
||||
paramKeys = append(paramKeys, "ipmi_password")
|
||||
case HOST_MNG_IP_ADDR, HOST_MNG_IP_ADDR_OPTIONAL:
|
||||
paramKeys = append(paramKeys, "access_ip")
|
||||
default:
|
||||
e := httperrors.NewInternalServerError("empty file content")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// skipped header row
|
||||
for _, row := range xlsx.GetRows("hosts")[1:] {
|
||||
if len(row) < 4 {
|
||||
log.Debugf("batchHostRegister row length too short (less than 4) %s", row)
|
||||
}
|
||||
|
||||
h = h + strings.Join(row, ",") + "\n"
|
||||
}
|
||||
|
||||
s := FetchSession(ctx, req, "")
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("hosts", jsonutils.NewString(h))
|
||||
resp, err := modules.Hosts.DoBatchRegister(s, params)
|
||||
resp, err := modules.Hosts.DoBatchRegister(s, paramKeys, params)
|
||||
if err != nil {
|
||||
e := httperrors.NewGeneralError(err)
|
||||
httperrors.JsonClientError(w, e)
|
||||
@@ -160,6 +205,117 @@ func (mh *MiscHandler) DoBatchHostRegister(ctx context.Context, w http.ResponseW
|
||||
appsrv.SendJSON(w, resp)
|
||||
}
|
||||
|
||||
func (mh *MiscHandler) DoBatchUserRegister(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
s := FetchSession(ctx, req, "")
|
||||
files := req.MultipartForm.File
|
||||
|
||||
userfiles, ok := files["users"]
|
||||
if !ok || len(userfiles) == 0 || userfiles[0] == nil {
|
||||
e := httperrors.NewInputParameterError("Missing parameter %s", "users")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
fileHeader := userfiles[0].Header
|
||||
contentType := fileHeader.Get("Content-Type")
|
||||
if contentType != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
|
||||
e := httperrors.NewInputParameterError("Wrong content type %s, required application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", contentType)
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := userfiles[0].Open()
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
e := httperrors.NewInternalServerError("can't open file")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
xlsx, err := excelize.OpenReader(file)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
e := httperrors.NewInternalServerError("can't parse file")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
// skipped header row
|
||||
rows := xlsx.GetRows("users")
|
||||
if len(rows) <= 1 {
|
||||
e := httperrors.NewInputParameterError("empty file")
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
users := []jsonutils.JSONObject{}
|
||||
names := map[string]bool{}
|
||||
domains := map[string]string{}
|
||||
for i, row := range rows[1:] {
|
||||
name := row[0]
|
||||
if len(name) == 0 {
|
||||
e := httperrors.NewClientError("row %d name is empty", i+2)
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := names[name]; ok {
|
||||
e := httperrors.NewClientError("duplicate name %s", row[0])
|
||||
httperrors.JsonClientError(w, e)
|
||||
return
|
||||
} else {
|
||||
names[name] = true
|
||||
_, err := modules.UsersV3.Get(s, name, nil)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
domainId, ok := domains[row[1]]
|
||||
if !ok {
|
||||
id, err := modules.Domains.GetId(s, row[1], nil)
|
||||
if err != nil {
|
||||
httperrors.JsonClientError(w, httperrors.NewGeneralError(err))
|
||||
return
|
||||
}
|
||||
|
||||
domainId = id
|
||||
domains[row[1]] = id
|
||||
}
|
||||
|
||||
user := jsonutils.NewDict()
|
||||
user.Add(jsonutils.NewString(name), "name")
|
||||
user.Add(jsonutils.NewString(domainId), "domain_id")
|
||||
user.Add(jsonutils.NewString("OneCloud@2019"), "password")
|
||||
if strings.ToLower(row[2]) == "true" || strings.ToLower(row[2]) == "1" {
|
||||
user.Add(jsonutils.JSONTrue, "allow_web_console")
|
||||
} else {
|
||||
user.Add(jsonutils.JSONFalse, "allow_web_console")
|
||||
}
|
||||
|
||||
users = append(users, user)
|
||||
}
|
||||
|
||||
// batch create
|
||||
var userG errgroup.Group
|
||||
for i := range users {
|
||||
user := users[i]
|
||||
userG.Go(func() error {
|
||||
_, err := modules.UsersV3.Create(s, user)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
if err := userG.Wait(); err != nil {
|
||||
e := httperrors.NewGeneralError(err)
|
||||
httperrors.GeneralServerError(w, e)
|
||||
return
|
||||
}
|
||||
|
||||
appsrv.SendJSON(w, jsonutils.NewDict())
|
||||
}
|
||||
|
||||
func (mh *MiscHandler) getDownloadsHandler(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
params := appctx.AppContextParams(ctx)
|
||||
template, ok := params["<template_id>"]
|
||||
@@ -168,23 +324,60 @@ func (mh *MiscHandler) getDownloadsHandler(ctx context.Context, w http.ResponseW
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var content bytes.Buffer
|
||||
switch template {
|
||||
case "BatchHostRegister":
|
||||
records := [][]string{{"MAC地址", "名称", "IPMI地址", "IPMI用户名", "IPMI密码"}}
|
||||
content, err := writeXlsx("hosts", records)
|
||||
records := [][]string{{HOST_MAC, HOST_NAME, HOST_IPMI_ADDR_OPTIONAL, HOST_IPMI_USERNAME_OPTIONAL, HOST_IPMI_PASSWORD_OPTIONAL}}
|
||||
content, err = writeXlsx("hosts", records)
|
||||
if err != nil {
|
||||
httperrors.InternalServerError(w, "internal server error")
|
||||
return
|
||||
}
|
||||
case "BatchHostISORegister":
|
||||
records := [][]string{{HOST_NAME, HOST_IPMI_ADDR, HOST_IPMI_USERNAME, HOST_IPMI_PASSWORD, HOST_MNG_IP_ADDR}}
|
||||
content, err = writeXlsx("hosts", records)
|
||||
if err != nil {
|
||||
httperrors.InternalServerError(w, "internal server error")
|
||||
return
|
||||
}
|
||||
case "BatchHostPXERegister":
|
||||
records := [][]string{{HOST_NAME, HOST_IPMI_ADDR, HOST_IPMI_USERNAME, HOST_IPMI_PASSWORD, HOST_MNG_IP_ADDR_OPTIONAL}}
|
||||
content, err = writeXlsx("hosts", records)
|
||||
if err != nil {
|
||||
httperrors.InternalServerError(w, "internal server error")
|
||||
return
|
||||
}
|
||||
case "BatchUserRegister":
|
||||
records := [][]string{{"用户名(user)", "部门/域(domain)", "是否登录控制台(allow_web_console:true、false)"}}
|
||||
content, err = writeXlsx("users", records)
|
||||
if err != nil {
|
||||
httperrors.InternalServerError(w, "internal server error")
|
||||
return
|
||||
}
|
||||
case "BatchProjectRegister":
|
||||
var titles []string
|
||||
if options.Options.NonDefaultDomainProjects {
|
||||
titles = []string{"项目名称", "域", "配额"}
|
||||
} else {
|
||||
titles = []string{"项目名称", "域"}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", "Attachment; filename=hosts_template.xlsx")
|
||||
w.Write(content.Bytes())
|
||||
return
|
||||
records := [][]string{titles}
|
||||
content, err = writeXlsx("projects", records)
|
||||
if err != nil {
|
||||
httperrors.InternalServerError(w, "internal server error")
|
||||
return
|
||||
}
|
||||
default:
|
||||
httperrors.InputParameterError(w, "template not found %s", template)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
w.Header().Set("Content-Disposition", "Attachment; filename=template.xlsx")
|
||||
w.Write(content.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
func (mh *MiscHandler) postPIUploads(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
@@ -16,7 +16,6 @@ package modules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
@@ -89,7 +88,7 @@ func (this *HostManager) GetIpmiInfo(s *mcclient.ClientSession, id string, param
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseHosts(data string) ([]jsonutils.JSONObject, string) {
|
||||
func parseHosts(titles []string, data string) ([]jsonutils.JSONObject, string) {
|
||||
msg := ""
|
||||
hosts := strings.Split(data, "\n")
|
||||
ret := []jsonutils.JSONObject{}
|
||||
@@ -101,37 +100,15 @@ func parseHosts(data string) ([]jsonutils.JSONObject, string) {
|
||||
}
|
||||
|
||||
fields := strings.Split(host, ",")
|
||||
if len(fields) != 5 {
|
||||
msg += fmt.Sprintf("第%d行: %s (格式不正确)\n", i, host)
|
||||
continue
|
||||
}
|
||||
|
||||
// mac address check
|
||||
if match, err := regexp.MatchString(MACAddressPattern, fields[0]); err != nil || !match {
|
||||
msg += fmt.Sprintf("第%d行: %s (Mac地址格式不正确)\n", i, host)
|
||||
continue
|
||||
}
|
||||
|
||||
// name check
|
||||
if len(fields[1]) == 0 {
|
||||
msg += fmt.Sprintf("第%d行: %s (名称不能为空)\n", i, host)
|
||||
}
|
||||
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(fields[0]), "access_mac")
|
||||
params.Add(jsonutils.NewString(fields[1]), "name")
|
||||
params.Add(jsonutils.NewString("baremetal"), "host_type")
|
||||
|
||||
if len(fields[2]) > 0 {
|
||||
params.Add(jsonutils.NewString(fields[2]), "ipmi_ip_addr")
|
||||
}
|
||||
|
||||
if len(fields[3]) > 0 {
|
||||
params.Add(jsonutils.NewString(fields[3]), "ipmi_username")
|
||||
}
|
||||
|
||||
if len(fields[4]) > 0 {
|
||||
params.Add(jsonutils.NewString(fields[4]), "ipmi_password")
|
||||
for i := range fields {
|
||||
field := fields[i]
|
||||
if len(field) > 0 {
|
||||
params.Add(jsonutils.NewString(field), titles[i])
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, params)
|
||||
@@ -140,13 +117,13 @@ func parseHosts(data string) ([]jsonutils.JSONObject, string) {
|
||||
return ret, msg
|
||||
}
|
||||
|
||||
func (this *HostManager) DoBatchRegister(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
func (this *HostManager) DoBatchRegister(s *mcclient.ClientSession, titles []string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
data, err := params.GetString("hosts")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hosts, msg := parseHosts(data)
|
||||
hosts, msg := parseHosts(titles, data)
|
||||
if len(msg) > 0 {
|
||||
return nil, httperrors.NewInputParameterError(msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user