| @@ -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) | |||
| @@ -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") | |||
| @@ -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) { | |||
| @@ -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" | |||
| @@ -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) | |||
| } | |||
| @@ -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 | |||
| @@ -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= | |||
| @@ -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" | |||
| ) | |||
| @@ -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{ | |||
| @@ -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") | |||
| } | |||
| } | |||
| } | |||
| @@ -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) | |||
| } | |||
| @@ -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("----------------------------------") | |||
| } | |||
| @@ -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") | |||
| } | |||
| } | |||
| } | |||
| @@ -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) | |||
| } | |||