diff --git a/client/internal/http/v1/server.go b/client/internal/http/v1/server.go index 1e141ba..dad99ee 100644 --- a/client/internal/http/v1/server.go +++ b/client/internal/http/v1/server.go @@ -48,6 +48,8 @@ func (s *Server) InitRouters(rt gin.IRoutes, ah *auth.Auth) { rt.POST(cliapi.UserSpaceDownloadPackagePath, certAuth, s.UserSpace().DownloadPackage) rt.POST(cliapi.UserSpaceCreatePackagePath, certAuth, s.UserSpace().CreatePackage) rt.GET(cliapi.UserSpaceGetPath, certAuth, s.UserSpace().Get) + rt.GET(cliapi.UserSpaceGetByNamePath, certAuth, s.UserSpace().GetByName) + rt.GET(cliapi.UserSpaceGetAllPath, certAuth, s.UserSpace().GetAll) rt.POST(cliapi.UserSpaceCreatePath, certAuth, s.UserSpace().Create) rt.POST(cliapi.UserSpaceUpdatePath, certAuth, s.UserSpace().Update) rt.POST(cliapi.UserSpaceDeletePath, certAuth, s.UserSpace().Delete) diff --git a/client/internal/http/v1/user_space.go b/client/internal/http/v1/user_space.go index 5351dc2..a3cef79 100644 --- a/client/internal/http/v1/user_space.go +++ b/client/internal/http/v1/user_space.go @@ -85,6 +85,43 @@ func (s *UserSpaceService) Get(ctx *gin.Context) { })) } +func (s *UserSpaceService) GetByName(ctx *gin.Context) { + log := logger.WithField("HTTP", "UserSpace.GetByName") + + var req cliapi.UserSpaceGetByName + if err := ctx.ShouldBindQuery(&req); err != nil { + log.Warnf("binding query: %s", err.Error()) + ctx.JSON(http.StatusBadRequest, types.Failed(ecode.BadArgument, "missing argument or invalid argument")) + return + } + + info, err := s.svc.UserSpaceSvc().GetByName(req.Name) + if err != nil { + log.Warnf("getting info: %s", err.Error()) + ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get userspace info failed")) + return + } + + ctx.JSON(http.StatusOK, types.OK(cliapi.UserSpaceGetByNameResp{ + UserSpace: info, + })) +} + +func (s *UserSpaceService) GetAll(ctx *gin.Context) { + log := logger.WithField("HTTP", "UserSpace.GetAll") + + allInfos, err := s.svc.UserSpaceSvc().GetAll() + if err != nil { + log.Warnf("getting info: %s", err.Error()) + ctx.JSON(http.StatusOK, types.Failed(ecode.OperationFailed, "get userspaces info failed")) + return + } + + ctx.JSON(http.StatusOK, types.OK(cliapi.UserSpaceGetAllResp{ + UserSpaces: allInfos, + })) +} + func (s *UserSpaceService) Create(ctx *gin.Context) { log := logger.WithField("HTTP", "UserSpace.Create") diff --git a/client/internal/services/user_space.go b/client/internal/services/user_space.go index 9a052a5..778a500 100644 --- a/client/internal/services/user_space.go +++ b/client/internal/services/user_space.go @@ -41,6 +41,10 @@ func (svc *UserSpaceService) GetByName(name string) (clitypes.UserSpace, error) return svc.DB.UserSpace().GetByName(svc.DB.DefCtx(), name) } +func (svc *UserSpaceService) GetAll() ([]clitypes.UserSpace, error) { + return svc.DB.UserSpace().GetAll(svc.DB.DefCtx()) +} + func (svc *UserSpaceService) Create(req cliapi.UserSpaceCreate) (*cliapi.UserSpaceCreateResp, *ecode.CodeError) { db2 := svc.DB space, err := db.DoTx01(db2, func(tx db.SQLContext) (clitypes.UserSpace, error) { diff --git a/client/sdk/api/v1/user_space.go b/client/sdk/api/v1/user_space.go index 001b618..f012ac3 100644 --- a/client/sdk/api/v1/user_space.go +++ b/client/sdk/api/v1/user_space.go @@ -78,6 +78,49 @@ func (c *Client) UserSpaceGet(req UserSpaceGet) (*UserSpaceGetResp, error) { return JSONAPI(&c.cfg, c.httpCli, &req, &UserSpaceGetResp{}) } +const UserSpaceGetByNamePath = "/userspace/getByName" + +type UserSpaceGetByName struct { + Name string `form:"name" url:"name" binding:"required"` +} + +func (r *UserSpaceGetByName) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, UserSpaceGetByNamePath, r) +} + +type UserSpaceGetByNameResp struct { + clitypes.UserSpace +} + +func (r *UserSpaceGetByNameResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + +func (c *Client) UserSpaceGetByName(req UserSpaceGetByName) (*UserSpaceGetByNameResp, error) { + return JSONAPI(&c.cfg, c.httpCli, &req, &UserSpaceGetByNameResp{}) +} + +const UserSpaceGetAllPath = "/userspace/getAll" + +type UserSpaceGetAll struct{} + +func (r *UserSpaceGetAll) MakeParam() *sdks.RequestParam { + return sdks.MakeQueryParam(http.MethodGet, UserSpaceGetAllPath, nil) +} + +type UserSpaceGetAllResp struct { + UserSpaces []clitypes.UserSpace `json:"userSpaces"` +} + +func (r *UserSpaceGetAllResp) ParseResponse(resp *http.Response) error { + return sdks.ParseCodeDataJSONResponse(resp, r) +} + +func (c *Client) UserSpaceGetAll() (*UserSpaceGetAllResp, error) { + req := UserSpaceGetAll{} + return JSONAPI(&c.cfg, c.httpCli, &req, &UserSpaceGetAllResp{}) +} + // 创建用户空间 const UserSpaceCreatePath = "/userspace/create" diff --git a/coordinator/types/storage_credential.go b/coordinator/types/storage_credential.go index 93ed77f..2b4a16e 100644 --- a/coordinator/types/storage_credential.go +++ b/coordinator/types/storage_credential.go @@ -1,12 +1,16 @@ package types import ( + "fmt" + "gitlink.org.cn/cloudream/common/pkgs/types" "gitlink.org.cn/cloudream/common/utils/serder" ) type StorageCredential interface { + // fmt.Stringer GetStorageCredentialType() string + String(showPassword bool) string } var _ = serder.UseTypeUnionInternallyTagged(types.Ref(types.NewTypeUnion[StorageCredential]( @@ -26,6 +30,10 @@ type LocalCred struct { RootDir string `json:"rootDir"` } +func (c *LocalCred) String(showPassword bool) string { + return fmt.Sprintf("RootDir=%s", c.RootDir) +} + // type MashupCred struct { // StorageCredential `json:"-"` // serder.Metadata `union:"Mashup"` @@ -41,6 +49,14 @@ type OSSCred struct { SK string `json:"secretAccessKey"` } +func (c *OSSCred) String(showPassword bool) string { + maskedSK := "****" + if showPassword { + maskedSK = c.SK + } + return fmt.Sprintf("AK=%s, SK=%s", c.AK, maskedSK) +} + type OBSCred struct { StorageCredential `json:"-"` serder.Metadata `union:"OBS"` @@ -49,6 +65,14 @@ type OBSCred struct { SK string `json:"secretAccessKey"` } +func (c *OBSCred) String(showPassword bool) string { + maskedSK := "****" + if showPassword { + maskedSK = c.SK + } + return fmt.Sprintf("AK=%s, SK=%s", c.AK, maskedSK) +} + type COSCred struct { StorageCredential `json:"-"` serder.Metadata `union:"COS"` @@ -57,6 +81,14 @@ type COSCred struct { SK string `json:"secretAccessKey"` } +func (c *COSCred) String(showPassword bool) string { + maskedSK := "****" + if showPassword { + maskedSK = c.SK + } + return fmt.Sprintf("AK=%s, SK=%s", c.AK, maskedSK) +} + type EFileCred struct { StorageCredential `json:"-"` serder.Metadata `union:"EFile"` @@ -69,6 +101,14 @@ type EFileCred struct { OrgID string `json:"orgID"` } +func (c *EFileCred) String(showPassword bool) string { + maskedSK := "****" + if showPassword { + maskedSK = c.Password + } + return fmt.Sprintf("TokenURL=%s, APIURL=%s, TokenExpire=%d, User=%s, Password=%s, OrgID=%s", c.TokenURL, c.APIURL, c.TokenExpire, c.User, maskedSK, c.OrgID) +} + // 通用的S3协议的存储服务 type S3Cred struct { StorageCredential `json:"-"` @@ -77,3 +117,11 @@ type S3Cred struct { AK string `json:"accessKeyId"` SK string `json:"secretAccessKey"` } + +func (c *S3Cred) String(showPassword bool) string { + maskedSK := "****" + if showPassword { + maskedSK = c.SK + } + return fmt.Sprintf("AK=%s, SK=%s", c.AK, maskedSK) +} diff --git a/go.mod b/go.mod index 3b29155..fe91ac5 100644 --- a/go.mod +++ b/go.mod @@ -49,19 +49,31 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.6 // indirect github.com/aws/smithy-go v1.22.2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/gabriel-vasile/mimetype v1.4.6 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-tty v0.0.3 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -69,6 +81,7 @@ require ( github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect @@ -81,6 +94,8 @@ require ( require ( github.com/antonfisher/nested-logrus-formatter v1.3.1 // indirect github.com/c-bata/go-prompt v0.2.6 + github.com/charmbracelet/bubbletea v1.3.5 + github.com/chzyer/readline v1.5.1 github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect diff --git a/go.sum b/go.sum index 62a1875..453204a 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0 h1:nyuzXooUNJexRT0Oy0UQY6AhOzxPx github.com/aws/aws-sdk-go-v2/service/s3 v1.71.0/go.mod h1:sT/iQz8JK3u/5gZkT+Hmr7GzVZehUMkRZpOaAwYXeGY= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= @@ -35,6 +37,22 @@ github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4 github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc= +github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= @@ -50,6 +68,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -143,6 +163,8 @@ github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3v github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -155,6 +177,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= @@ -171,6 +195,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -225,6 +255,8 @@ github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zyedidia/generic v1.2.1 h1:Zv5KS/N2m0XZZiuLS82qheRG4X1o5gsWreGb0hR7XDc= @@ -291,6 +323,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/jcsctl/cmd/all/all.go b/jcsctl/cmd/all/all.go index 9db340d..7d60c60 100644 --- a/jcsctl/cmd/all/all.go +++ b/jcsctl/cmd/all/all.go @@ -2,4 +2,5 @@ package all import ( _ "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd/bucket" + _ "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd/userspace" ) diff --git a/jcsctl/cmd/cmd.go b/jcsctl/cmd/cmd.go index ef93a3d..669175c 100644 --- a/jcsctl/cmd/cmd.go +++ b/jcsctl/cmd/cmd.go @@ -74,7 +74,7 @@ func RootExecute() { } if endpoint == "" { - endpoint = "https://127.0.0.1:7890" + endpoint = "https://127.0.0.1:8890" } cli := cliapi.NewClient(api.Config{ diff --git a/jcsctl/cmd/userspace/create.go b/jcsctl/cmd/userspace/create.go new file mode 100644 index 0000000..71d2e30 --- /dev/null +++ b/jcsctl/cmd/userspace/create.go @@ -0,0 +1,563 @@ +package userspace + +import ( + "fmt" + "strconv" + "strings" + + "github.com/chzyer/readline" + "github.com/spf13/cobra" + "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" + + cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types" + "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd" +) + +type MyUserSpace struct { + api.UserSpaceCreate +} + +func init() { + cmd := cobra.Command{ + Use: "create", + Short: "add a new cloud storage", + Run: func(c *cobra.Command, args []string) { + ctx := cmd.GetCmdCtx(c) + create(c, ctx) + }, + } + UserSpaceCmd.AddCommand(&cmd) +} + +func create(c *cobra.Command, ctx *cmd.CommandContext) { + var userSpace MyUserSpace + + rl, err := readline.New("> ") + if err != nil { + fmt.Printf("初始化命令行失败: %v\n", err) + return + } + defer rl.Close() + + rl.SetPrompt("\033[36m请输入存储服务名称(Name): \033[0m") + name, err := rl.Readline() + if err != nil { + return + } + userSpace.Name = name + + storageType, err := promptSelectStorage() + if err != nil { + return + } + + switch storageType { + case "Local": + err = userSpace.collectLocalConfig(rl) + case "OBS": + err = userSpace.collectObsConfig(rl) + case "OSS": + err = userSpace.collectOssConfig(rl) + case "COS": + err = userSpace.collectCosConfig(rl) + case "EFile": + err = userSpace.collectEfileConfig(rl) + case "S3": + err = userSpace.collectS3Config(rl) + } + if err != nil { + return + } + + err = userSpace.collectShardStore(rl) + if err != nil { + return + } + + err = userSpace.collectWorkingDir(rl) + if err != nil { + return + } + + _, err = ctx.Client.UserSpaceCreate(userSpace.UserSpaceCreate) + if err != nil { + fmt.Printf("\033[31m保存配置失败: %v\033[0m", err) + return + } + fmt.Println("\033[32m配置保存成功!\033[0m") +} + +func promptSelectStorage() (string, error) { + rl, _ := readline.NewEx(&readline.Config{ + Prompt: "\033[36m»\033[0m ", + HistoryFile: "/tmp/storage_history.txt", + InterruptPrompt: "^C", + }) + defer rl.Close() + + fmt.Println("\033[1;36m请选择存储类型(StorageType):\033[0m") + options := []string{"Local", "OBS", "OSS", "COS", "EFile", "S3"} + for i, option := range options { + fmt.Printf("\033[33m%d. %s\033[0m\n", i+1, option) + } + + for { + line, err := rl.Readline() + if err != nil { + return "", err + } + + trimmed := strings.TrimSpace(line) + switch trimmed { + case "1": + return "Local", nil + case "2": + return "OBS", nil + case "3": + return "OSS", nil + case "4": + return "COS", nil + case "5": + return "EFile", nil + case "6": + return "S3", nil + default: + fmt.Printf("\033[31m错误: 无效选项 '%s',请输入序号!\033[0m\n", line) + } + } +} + +func (userSpace *MyUserSpace) collectLocalConfig(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入StorageName: \033[0m") + storageName, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Location: \033[0m") + location, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入RootDir: \033[0m") + rootDir, err := rl.Readline() + if err != nil { + return err + } + + userSpace.Storage = &cortypes.LocalType{ + Type: "Local", + Location: cortypes.Location{ + StorageName: storageName, + Location: location, + }, + } + userSpace.Credential = &cortypes.LocalCred{ + Type: "Local", + RootDir: rootDir, + } + return nil +} + +func (userSpace *MyUserSpace) collectObsConfig(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入Region: \033[0m") + region, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Endpoint: \033[0m") + endpoint, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Bucket: \033[0m") + bucket, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入ProjectID: \033[0m") + projectID, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Storage = &cortypes.OBSType{ + Type: "OBS", + Region: region, + Endpoint: endpoint, + Bucket: bucket, + ProjectID: projectID, + } + userSpace.Credential = &cortypes.OBSCred{ + Type: "OBS", + AK: accessKey, + SK: secretKey, + } + + for { + rl.SetPrompt("\033[36m是否支持存储服务间直传文件?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.S2STransferFeature{ + Type: "S2STransfer", + }) + return nil + case "n", "no": + fmt.Println("\033[36m不支持存储服务间直传文件 \033[0m") + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *MyUserSpace) collectOssConfig(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入Region: \033[0m") + region, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Endpoint: \033[0m") + endpoint, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Bucket: \033[0m") + bucket, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Storage = &cortypes.OSSType{ + Region: region, + Endpoint: endpoint, + Bucket: bucket, + } + userSpace.Credential = &cortypes.OSSCred{ + Type: "OSS", + AK: accessKey, + SK: secretKey, + } + return nil +} + +func (userSpace *MyUserSpace) collectCosConfig(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入Region: \033[0m") + region, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Endpoint: \033[0m") + endpoint, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Bucket: \033[0m") + bucket, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Storage = &cortypes.COSType{ + Type: "COS", + Region: region, + Endpoint: endpoint, + Bucket: bucket, + } + userSpace.Credential = &cortypes.COSCred{ + Type: "COS", + AK: accessKey, + SK: secretKey, + } + return nil +} + +func (userSpace *MyUserSpace) collectEfileConfig(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入ClusterID: \033[0m") + clusterID, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入TokenURL: \033[0m") + tokenURL, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入APIURL: \033[0m") + apiURL, err := rl.Readline() + if err != nil { + return err + } + + tokenExpire := 0 + for { + rl.SetPrompt("\033[36m请输入TokenExpire: \033[0m") + valueInt, err := rl.Readline() + if err != nil { + return err + } + if strings.TrimSpace(valueInt) == "" { + fmt.Println("\033[31m错误:输入不能为空,请输入正整数\033[0m") + continue + } + num, err := strconv.ParseInt(valueInt, 10, 64) + if err != nil { + fmt.Printf("\033[31m错误:'%s' 不是有效整数,请输入正整数\033[0m\n", valueInt) + continue + } + if num <= 0 { + fmt.Printf("\033[31m错误:%d 不是正整数,请输入大于 0 的整数\033[0m\n", num) + continue + } + tokenExpire = int(num) + break + } + + rl.SetPrompt("\033[36m请输入User: \033[0m") + user, err := rl.Readline() + if err != nil { + return err + } + + passwordBytes, err := rl.ReadPassword("\033[36m请输入Password: \033[0m") + if err != nil { + return err + } + password := string(passwordBytes) + + rl.SetPrompt("\033[36m请输入OrgID: \033[0m") + orgID, err := rl.Readline() + if err != nil { + return err + } + + userSpace.Storage = &cortypes.EFileType{ + Type: "EFile", + ClusterID: clusterID, + } + userSpace.Credential = &cortypes.EFileCred{ + Type: "EFile", + TokenURL: tokenURL, + APIURL: apiURL, + TokenExpire: tokenExpire, + User: user, + Password: password, + OrgID: orgID, + } + + for { + rl.SetPrompt("\033[36m是否提供能进行EC计算的接口?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.ECMultiplierFeature{ + Type: "ECMultiplier", + }) + return nil + case "n", "no": + fmt.Println("\033[36m未提供能进行EC计算的接口 \033[0m") + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *MyUserSpace) collectS3Config(rl *readline.Instance) error { + var err error + rl.SetPrompt("\033[36m请输入Region: \033[0m") + region, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Endpoint: \033[0m") + endpoint, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入Bucket: \033[0m") + bucket, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Storage = &cortypes.S3Type{ + Type: "S3", + Region: region, + Endpoint: endpoint, + Bucket: bucket, + } + userSpace.Credential = &cortypes.S3Cred{ + Type: "S3", + AK: accessKey, + SK: secretKey, + } + + for { + rl.SetPrompt("\033[36m是否支持分段上传?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.MultipartUploadFeature{ + Type: "MultipartUpload", + }) + return nil + case "n", "no": + fmt.Println("\033[36m不支持分段上传 \033[0m") + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *MyUserSpace) collectShardStore(rl *readline.Instance) error { + for { + rl.SetPrompt("\033[36m是否开启分片存储功能?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + for { + rl.SetPrompt("\033[36m请输入最大Size: \033[0m") + sizeInput, err := rl.Readline() + if err != nil { + return err + } + if strings.TrimSpace(sizeInput) == "" { + fmt.Println("\033[31m错误:输入不能为空31m错误:输入不能为空,请输入正整数\033[0m") + continue + } + + maxSize, err := strconv.ParseInt(sizeInput, 10, 64) + if err != nil { + fmt.Printf("\033[31m错误:'%s' 不是有效整数,请输入正整数\033[0m\n", sizeInput) + continue + } + if maxSize <= 0 { + fmt.Printf("\033[31m错误:%d 不是正整数,请输入大于 0 的整数\033[0m\n", maxSize) + continue + } + userSpace.ShardStore = &cortypes.ShardStoreUserConfig{ + MaxSize: maxSize, + } + return nil + } + + case "n", "no": + fmt.Println("\033[31m分片存储未启用 \033[0m") + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *MyUserSpace) collectWorkingDir(rl *readline.Instance) error { + for { + rl.SetPrompt("\033[36m默认工作路径(WorkingDir)为jcs,是否修改?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + rl.SetPrompt("\033[36m请输入新的工作路径(WorkingDir): \033[0m") + newValue, err := rl.Readline() + if err != nil { + return err + } + if newValue != "" { + userSpace.WorkingDir = newValue + } + return nil + case "n", "no": + userSpace.WorkingDir = "jcs" + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} diff --git a/jcsctl/cmd/userspace/delete.go b/jcsctl/cmd/userspace/delete.go new file mode 100644 index 0000000..ad1e462 --- /dev/null +++ b/jcsctl/cmd/userspace/delete.go @@ -0,0 +1,111 @@ +package userspace + +import ( + "fmt" + "strconv" + "strings" + + "github.com/chzyer/readline" + "github.com/spf13/cobra" + cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" + clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" + "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd" +) + +func init() { + cmd := cobra.Command{ + Use: "delete", + Short: "delete a cloud storage", + Run: func(c *cobra.Command, args []string) { + ctx := cmd.GetCmdCtx(c) + delete(c, ctx) + }, + } + UserSpaceCmd.AddCommand(&cmd) +} + +func delete(c *cobra.Command, ctx *cmd.CommandContext) { + rl, err := readline.New("> ") + if err != nil { + fmt.Printf("\033[31m初始化命令行失败: %v\033[0m\n", err) + return + } + defer rl.Close() + + fmt.Printf("\033[1;36m请选择删除依据: \033[0m\n") + options := []string{"ID", "Name"} + for i, option := range options { + fmt.Printf("\033[33m%d. %s\033[0m\n", i+1, option) + } + + rl.SetPrompt("\033[36m> \033[0m") + line, err := rl.Readline() + if err != nil { + return + } + + var userSpace clitypes.UserSpace + trimmed := strings.TrimSpace(line) + switch trimmed { + case "1": + rl.SetPrompt("\033[36m请输入云存储ID(ID): \033[0m") + idInput, err := rl.Readline() + if err != nil || idInput == "" { + return + } + + id, err := strconv.ParseInt(strings.TrimSpace(idInput), 10, 64) + if err != nil { + fmt.Printf("\033[31mID 格式错误: %v\033[0m\n", err) + return + } + + resp, err := ctx.Client.UserSpaceGet(cliapi.UserSpaceGet{ + UserSpaceID: clitypes.UserSpaceID(id), + }) + if err != nil { + fmt.Printf("\033[31m保存配置失败: %v\033[0m", err) + return + } + userSpace = resp.UserSpace + + case "2": + rl.SetPrompt("\033[36m请输入云存储名称(Name): \033[0m") + nameInput, err := rl.Readline() + if err != nil || nameInput == "" { + fmt.Println("\033[31m输入已取消\033[0m") + return + } + name := strings.TrimSpace(nameInput) + + resp, err := ctx.Client.UserSpaceGetByName(cliapi.UserSpaceGetByName{ + Name: name, + }) + if err != nil { + fmt.Printf("\033[31m保存配置失败: %v\033[0m", err) + return + } + userSpace = resp.UserSpace + + default: + fmt.Printf("\033[31m错误: 无效选项 '%s',请输入序号!\033[0m\n", line) + } + + fmt.Println("\n\033[1;36m找到云存储:\033[0m") + fmt.Printf("\033[1;36mID:%d 名称:%s 类型:%s\033[0m\n", userSpace.UserSpaceID, userSpace.Name, userSpace.Storage.GetStorageType()) + + rl.SetPrompt("\033[31m确认删除?(y/n): \033[0m") + confirm, err := rl.Readline() + if err != nil || strings.ToLower(strings.TrimSpace(confirm)) != "y" { + return + } + + _, err = ctx.Client.UserSpaceDelete(cliapi.UserSpaceDelete{ + UserSpaceID: userSpace.UserSpaceID, + }) + if err != nil { + fmt.Printf("\033[31m删除失败: %v\033[0m\n", err) + return + } + fmt.Printf("\n\033[32m删除成功: %s\033[0m\n", userSpace.Name) +} diff --git a/jcsctl/cmd/userspace/ls.go b/jcsctl/cmd/userspace/ls.go new file mode 100644 index 0000000..067ceaa --- /dev/null +++ b/jcsctl/cmd/userspace/ls.go @@ -0,0 +1,106 @@ +package userspace + +import ( + "fmt" + "strconv" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/spf13/cobra" + cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" + clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" + "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd" +) + +func init() { + var opt lsOpt + cmd := cobra.Command{ + Use: "ls", + Args: cobra.MaximumNArgs(1), + Run: func(c *cobra.Command, args []string) { + ctx := cmd.GetCmdCtx(c) + ls(c, ctx, opt, args) + }, + } + + cmd.Flags().BoolVarP(&opt.ByID, "id", "", false, "按id查询") + cmd.Flags().BoolVarP(&opt.ShowPassword, "password", "p", false, "显示密码信息,请在安全环境下使用") + UserSpaceCmd.AddCommand(&cmd) +} + +type lsOpt struct { + ByID bool + ShowPassword bool +} + +func ls(c *cobra.Command, ctx *cmd.CommandContext, opt lsOpt, args []string) { + // 仅ls无参数 + if len(args) == 0 { + resp, err := ctx.Client.UserSpaceGetAll() + if err != nil { + cmd.ErrorExitln(err.Error()) + return + } + + fmt.Printf("total: %d\n", len(resp.UserSpaces)) + tb := table.NewWriter() + tb.AppendHeader(table.Row{"ID", "Name", "StorageType"}) + for _, userSpace := range resp.UserSpaces { + tb.AppendRow(table.Row{userSpace.UserSpaceID, userSpace.Name, userSpace.Storage.GetStorageType()}) + } + fmt.Println(tb.Render()) + return + } + + searchKey := args[0] + var userSpace *clitypes.UserSpace + if opt.ByID { + id, err := strconv.Atoi(searchKey) + if err != nil { + cmd.ErrorExitln("ID必须是数字") + return + } + + result, err := ctx.Client.UserSpaceGet(cliapi.UserSpaceGet{ + UserSpaceID: clitypes.UserSpaceID(id), + }) + if err != nil { + cmd.ErrorExitln(err.Error()) + return + } + userSpace = &result.UserSpace + + } else { + result, err := ctx.Client.UserSpaceGetByName(cliapi.UserSpaceGetByName{ + Name: searchKey, + }) + if err != nil { + cmd.ErrorExitln(err.Error()) + return + } + userSpace = &result.UserSpace + } + + if userSpace == nil { + cmd.ErrorExitln(fmt.Sprintf("未找到匹配的云存储: %s", searchKey)) + return + } + + fmt.Println("\n\033[1;36m云存储详情\033[0m") + fmt.Println("----------------------------------") + fmt.Printf("\033[1m%-8s\033[0m %d\n", "ID:", userSpace.UserSpaceID) + fmt.Printf("\033[1m%-8s\033[0m %s\n", "名称:", userSpace.Name) + fmt.Printf("\033[1m%-8s\033[0m %s\n", "类型:", userSpace.Storage.GetStorageType()) + fmt.Printf("\033[1m%-8s\033[0m %s\n", "Location:", userSpace.Storage.GetLocation().Location) + if opt.ShowPassword { + fmt.Printf("\033[1m%-8s\033[0m %s\n", "Credential:", userSpace.Credential.String(true)) + } else { + fmt.Printf("\033[1m%-8s\033[0m %s\n", "Credential:", userSpace.Credential.String(false)) + } + + if len(userSpace.Features) > 0 { + fmt.Printf("\033[1m%-8s\033[0m %s\n", "Features:", userSpace.Features[0].GetFeatureType()) + } + fmt.Printf("\033[1m%-8s\033[0m %s\n", "WorkingDir:", userSpace.WorkingDir) + fmt.Println("----------------------------------") + +} diff --git a/jcsctl/cmd/userspace/update.go b/jcsctl/cmd/userspace/update.go new file mode 100644 index 0000000..2145688 --- /dev/null +++ b/jcsctl/cmd/userspace/update.go @@ -0,0 +1,321 @@ +package userspace + +import ( + "fmt" + "strconv" + "strings" + + "github.com/chzyer/readline" + "github.com/spf13/cobra" + "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" + cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1" + clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" + cortypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types" + "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd" +) + +type UserSpaceUpdate struct { + api.UserSpaceUpdate +} + +func init() { + cmd := cobra.Command{ + Use: "update", + Short: "update a new cloud storage", + Run: func(c *cobra.Command, args []string) { + ctx := cmd.GetCmdCtx(c) + update(c, ctx) + }, + } + UserSpaceCmd.AddCommand(&cmd) +} + +func update(c *cobra.Command, ctx *cmd.CommandContext) { + rl, err := readline.New("> ") + if err != nil { + fmt.Printf("\033[31m初始化命令行失败: %v\033[0m\n", err) + return + } + defer rl.Close() + + rl.SetPrompt("\033[36m请输入云存储ID(ID): \033[0m") + idInput, err := rl.Readline() + if err != nil || idInput == "" { + return + } + + id, err := strconv.ParseInt(strings.TrimSpace(idInput), 10, 64) + if err != nil { + fmt.Printf("\033[31mID 格式错误: %v\033[0m\n", err) + return + } + + resp, err := ctx.Client.UserSpaceGet(cliapi.UserSpaceGet{ + UserSpaceID: clitypes.UserSpaceID(id), + }) + if err != nil { + fmt.Printf("\033[31m云存储id=%d 不存在: %v\033[0m\n", id, err) + return + } + + rl.SetPrompt("\033[36m请输入修改后的存储服务名称(Name): \033[0m") + name, err := rl.Readline() + if err != nil { + return + } + + var userSpaceUpdate UserSpaceUpdate + userSpaceUpdate.UserSpaceID = clitypes.UserSpaceID(id) + userSpaceUpdate.Name = name + + storageType := resp.UserSpace.Storage.GetStorageType() + switch storageType { + case "Local": + err = userSpaceUpdate.collectLocalConfig(rl) + case "OBS": + err = userSpaceUpdate.collectObsConfig(rl) + case "OSS": + err = userSpaceUpdate.collectOssConfig(rl) + case "COS": + err = userSpaceUpdate.collectCosConfig(rl) + case "EFile": + err = userSpaceUpdate.collectEfileConfig(rl) + case "S3": + err = userSpaceUpdate.collectS3Config(rl) + } + if err != nil { + return + } + + _, err = ctx.Client.UserSpaceUpdate(userSpaceUpdate.UserSpaceUpdate) + if err != nil { + fmt.Printf("\033[31m更新配置失败: %v\033[0m", err) + return + } + fmt.Println("\033[32m配置更新成功!\033[0m") +} + +func (userSpace *UserSpaceUpdate) collectLocalConfig(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入RootDir: \033[0m") + rootDir, err := rl.Readline() + if err != nil { + return err + } + + userSpace.Credential = &cortypes.LocalCred{ + Type: "Local", + RootDir: rootDir, + } + return nil +} + +func (userSpace *UserSpaceUpdate) collectObsConfig(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Credential = &cortypes.OBSCred{ + Type: "OBS", + AK: accessKey, + SK: secretKey, + } + + for { + rl.SetPrompt("\033[36m是否支持存储服务间直传文件?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.S2STransferFeature{ + Type: "S2STransfer", + }) + return nil + case "n", "no": + userSpace.Features = nil + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *UserSpaceUpdate) collectOssConfig(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Credential = &cortypes.OSSCred{ + Type: "OSS", + AK: accessKey, + SK: secretKey, + } + return nil +} + +func (userSpace *UserSpaceUpdate) collectCosConfig(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Credential = &cortypes.COSCred{ + Type: "COS", + AK: accessKey, + SK: secretKey, + } + return nil +} + +func (userSpace *UserSpaceUpdate) collectEfileConfig(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入TokenURL: \033[0m") + tokenURL, err := rl.Readline() + if err != nil { + return err + } + + rl.SetPrompt("\033[36m请输入APIURL: \033[0m") + apiURL, err := rl.Readline() + if err != nil { + return err + } + + tokenExpire := 0 + for { + rl.SetPrompt("\033[36m请输入TokenExpire: \033[0m") + valueInt, err := rl.Readline() + if err != nil { + return err + } + if strings.TrimSpace(valueInt) == "" { + fmt.Println("\033[31m错误:输入不能为空,请输入正整数\033[0m") + continue + } + num, err := strconv.ParseInt(valueInt, 10, 64) + if err != nil { + fmt.Printf("\033[31m错误:'%s' 不是有效整数,请输入正整数\033[0m\n", valueInt) + continue + } + if num <= 0 { + fmt.Printf("\033[31m错误:%d 不是正整数,请输入大于 0 的整数\033[0m\n", num) + continue + } + tokenExpire = int(num) + break + } + + rl.SetPrompt("\033[36m请输入User: \033[0m") + user, err := rl.Readline() + if err != nil { + return err + } + + passwordBytes, err := rl.ReadPassword("\033[36m请输入Password: \033[0m") + if err != nil { + return err + } + password := string(passwordBytes) + + rl.SetPrompt("\033[36m请输入OrgID: \033[0m") + orgID, err := rl.Readline() + if err != nil { + return err + } + + userSpace.Credential = &cortypes.EFileCred{ + Type: "EFile", + TokenURL: tokenURL, + APIURL: apiURL, + TokenExpire: tokenExpire, + User: user, + Password: password, + OrgID: orgID, + } + + for { + rl.SetPrompt("\033[36m是否提供能进行EC计算的接口?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.ECMultiplierFeature{ + Type: "ECMultiplier", + }) + return nil + case "n", "no": + userSpace.Features = nil + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} + +func (userSpace *UserSpaceUpdate) collectS3Config(rl *readline.Instance) error { + rl.SetPrompt("\033[36m请输入AccessKeyID: \033[0m") + accessKey, err := rl.Readline() + if err != nil { + return err + } + + secretBytes, err := rl.ReadPassword("\033[36m请输入AccessKeySecret: \033[0m") + if err != nil { + return err + } + secretKey := string(secretBytes) + + userSpace.Credential = &cortypes.S3Cred{ + Type: "S3", + AK: accessKey, + SK: secretKey, + } + + for { + rl.SetPrompt("\033[36m是否支持分段上传?(y/n): \033[0m") + input, err := rl.Readline() + if err != nil { + return err + } + + switch strings.ToLower(strings.TrimSpace(input)) { + case "y", "yes": + userSpace.Features = append(userSpace.Features, &cortypes.MultipartUploadFeature{ + Type: "MultipartUpload", + }) + return nil + case "n", "no": + userSpace.Features = nil + return nil + default: + fmt.Println("\033[31m无效输入!请输入 y/n 或 yes/no \033[0m") + } + } +} diff --git a/jcsctl/cmd/userspace/userspace.go b/jcsctl/cmd/userspace/userspace.go new file mode 100644 index 0000000..fc01b3c --- /dev/null +++ b/jcsctl/cmd/userspace/userspace.go @@ -0,0 +1,14 @@ +package userspace + +import ( + "github.com/spf13/cobra" + "gitlink.org.cn/cloudream/jcs-pub/jcsctl/cmd" +) + +var UserSpaceCmd = &cobra.Command{ + Use: "userspace", +} + +func init() { + cmd.RootCmd.AddCommand(UserSpaceCmd) +}