diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 1b9449a3d5..180d47f326 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -15,10 +15,13 @@ import ( "github.com/ucloud/ucloud-cli/products/subnet" "github.com/ucloud/ucloud-cli/products/udisk" "github.com/ucloud/ucloud-cli/products/udpn" + "github.com/ucloud/ucloud-cli/products/ufs" "github.com/ucloud/ucloud-cli/products/uhost" "github.com/ucloud/ucloud-cli/products/ulb" "github.com/ucloud/ucloud-cli/products/umodelverse" + "github.com/ucloud/ucloud-cli/products/upfs" "github.com/ucloud/ucloud-cli/products/uphost" + "github.com/ucloud/ucloud-cli/products/usnap" "github.com/ucloud/ucloud-cli/products/vpc" ) @@ -37,10 +40,13 @@ func registeredProducts() []cli.Product { subnet.New(), udisk.New(), udpn.New(), + ufs.New(), uhost.New(), ulb.New(), umodelverse.New(), + upfs.New(), uphost.New(), + usnap.New(), vpc.New(), } } diff --git a/go.mod b/go.mod index 2f7314cb07..fdf0db06af 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/ucloud/ucloud-sdk-go v0.22.86 + github.com/ucloud/ucloud-sdk-go v0.22.87 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index bbfaef96ac..0a0eb8db2d 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ucloud/ucloud-sdk-go v0.22.86 h1:V8lCn0qhKyGgZYSobmKX5+kUJX5QFqh1SWw1yngqXC8= -github.com/ucloud/ucloud-sdk-go v0.22.86/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= +github.com/ucloud/ucloud-sdk-go v0.22.87 h1:IAQBH1bl27+nBlBFo/hx5mMuDOMQHFks7cpOTfgxK3U= +github.com/ucloud/ucloud-sdk-go v0.22.87/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/products/udisk/product.yaml b/products/udisk/product.yaml index b77f48efa3..66e2b8ba60 100644 --- a/products/udisk/product.yaml +++ b/products/udisk/product.yaml @@ -1,7 +1,7 @@ # products/udisk/product.yaml — udisk 产品元数据(归属真源,owner 自治维护) name: udisk owners: - - Episkey-G + - pearlinpan commands: - udisk enabled: true diff --git a/products/ufs/internal/ufs/cmd.go b/products/ufs/internal/ufs/cmd.go new file mode 100644 index 0000000000..8fc245f82c --- /dev/null +++ b/products/ufs/internal/ufs/cmd.go @@ -0,0 +1,20 @@ +package ufs + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `ufs` root command and mounts the subcommands. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "ufs", + Short: "Manage UFS (UCloud File Storage) volumes", + Long: "Manage UFS (UCloud File Storage) volumes", + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDescribe(ctx)) + cmd.AddCommand(newDelete(ctx)) + return cmd +} diff --git a/products/ufs/internal/ufs/create.go b/products/ufs/internal/ufs/create.go new file mode 100644 index 0000000000..68b84b9c4d --- /dev/null +++ b/products/ufs/internal/ufs/create.go @@ -0,0 +1,58 @@ +package ufs + +import ( + "fmt" + + "github.com/spf13/cobra" + + ufssdk "github.com/ucloud/ucloud-sdk-go/services/ufs" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud ufs create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ufssdk.NewClient) + req := client.NewCreateUFSVolumeRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a UFS volume", + Long: "Create a UFS volume", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + resp, err := client.CreateUFSVolume(req) + if err != nil { + ctx.HandleError(err) + return + } + + text := fmt.Sprintf("ufs:%v created", resp.VolumeId) + fmt.Fprintln(w, text) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.VolumeId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VolumeName = flags.String("name", "", "Required. Name of the UFS volume to create") + req.Size = flags.Int("size-gb", 100, "Required. Size of the UFS volume. Unit: GB") + req.StorageType = flags.String("storage-type", "Basic", "Optional. Storage type: 'Basic' (capacity) or 'Advanced' (performance)") + req.ProtocolType = flags.String("protocol-type", "NFS", "Optional. Protocol type: 'NFS' or 'SMB'") + req.ChargeType = flags.String("charge-type", "Dynamic", "Optional. 'Year', pay yearly; 'Month', pay monthly; 'Dynamic', pay hourly") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months") + req.Tag = flags.String("group", "Default", "Optional. Business group") + req.Remark = flags.String("remark", "", "Optional. Remark") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic", "Trial") + command.SetFlagValues(cmd, "storage-type", "Basic", "Advanced") + command.SetFlagValues(cmd, "protocol-type", "NFS", "SMB") + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("size-gb") + + return cmd +} diff --git a/products/ufs/internal/ufs/delete.go b/products/ufs/internal/ufs/delete.go new file mode 100644 index 0000000000..d472b2d389 --- /dev/null +++ b/products/ufs/internal/ufs/delete.go @@ -0,0 +1,59 @@ +package ufs + +import ( + "fmt" + + "github.com/spf13/cobra" + + ufssdk "github.com/ucloud/ucloud-sdk-go/services/ufs" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud ufs delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes *bool + var volumeIDs *[]string + client := cli.NewServiceClient(ctx, ufssdk.NewClient) + req := client.NewRemoveUFSVolumeRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete UFS volume(s)", + Long: "Delete UFS volume(s)", + Run: func(cmd *cobra.Command, args []string) { + ok, err := ctx.Confirm(*yes, "Are you sure to delete UFS volume(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, id := range *volumeIDs { + id := ctx.PickResourceID(id) + req.VolumeId = &id + _, err := client.RemoveUFSVolume(req) + if err != nil { + ctx.HandleError(err) + continue + } else { + fmt.Fprintf(w, "ufs[%s] deleted\n", *req.VolumeId) + results = append(results, cli.OpResultRow{ResourceID: *req.VolumeId, Action: "delete", Status: "Deleted"}) + } + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + volumeIDs = flags.StringSlice("volume-id", nil, "Required. The Resource ID of UFS volumes to delete") + yes = flags.BoolP("yes", "y", false, "Optional. Do not prompt for confirmation.") + + ctx.BindCommonParams(cmd, req) + + cmd.MarkFlagRequired("volume-id") + + return cmd +} diff --git a/products/ufs/internal/ufs/describe.go b/products/ufs/internal/ufs/describe.go new file mode 100644 index 0000000000..afa7eb4ad7 --- /dev/null +++ b/products/ufs/internal/ufs/describe.go @@ -0,0 +1,83 @@ +package ufs + +import ( + "fmt" + + "github.com/spf13/cobra" + + ufssdk "github.com/ucloud/ucloud-sdk-go/services/ufs" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDescribe ucloud ufs describe +func newDescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ufssdk.NewClient) + req := client.NewDescribeUFSVolume2Request() + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe UFS volume(s)", + Long: "Describe UFS volume(s)", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.DescribeUFSVolume2(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []VolumeRow{} + for _, vol := range resp.DataSet { + row := VolumeRow{ + ResourceID: vol.VolumeId, + Name: vol.VolumeName, + Group: vol.Tag, + Size: fmt.Sprintf("%dGB", vol.Size), + UsedSize: fmt.Sprintf("%dGB", vol.UsedSize), + ProtocolType: vol.ProtocolType, + StorageType: vol.StorageType, + MountPoints: fmt.Sprintf("%d/%d", vol.TotalMountPointNum, vol.MaxMountPointNum), + State: vol.IsExpired, + CreationTime: common.FormatDate(vol.CreateTime), + Expiration: common.FormatDate(vol.ExpiredTime), + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VolumeId = flags.String("volume-id", "", "Optional. Resource ID of the UFS volume") + req.Limit = flags.Int("limit", 50, "Optional. Limit") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + return cmd +} + +// describeUfsByID returns the poller's describe func, closing over ctx so it +// can build an authed ufs client. +func describeUfsByID(ctx *cli.Context) func(volumeID string, commonBase *request.CommonBase) (interface{}, error) { + return func(volumeID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, ufssdk.NewClient) + req := client.NewDescribeUFSVolume2Request() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.VolumeId = &volumeID + limit := 50 + req.Limit = &limit + resp, err := client.DescribeUFSVolume2(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) < 1 { + return nil, nil + } + return &resp.DataSet[0], nil + } +} diff --git a/products/ufs/internal/ufs/rows.go b/products/ufs/internal/ufs/rows.go new file mode 100644 index 0000000000..dcf95e3c5b --- /dev/null +++ b/products/ufs/internal/ufs/rows.go @@ -0,0 +1,16 @@ +package ufs + +// VolumeRow represents a single row in the ufs volume list output. +type VolumeRow struct { + ResourceID string + Name string + Group string + Size string + UsedSize string + ProtocolType string + StorageType string + MountPoints string + State string + CreationTime string + Expiration string +} diff --git a/products/ufs/internal/ufs/status.go b/products/ufs/internal/ufs/status.go new file mode 100644 index 0000000000..6d14f6ed02 --- /dev/null +++ b/products/ufs/internal/ufs/status.go @@ -0,0 +1,8 @@ +package ufs + +// UFS-domain state constants. +const ( + VOLUME_CREATING = "Creating" + VOLUME_AVAILABLE = "Available" + VOLUME_FAILED = "Failed" +) diff --git a/products/ufs/product.go b/products/ufs/product.go new file mode 100644 index 0000000000..9d03bbe6c1 --- /dev/null +++ b/products/ufs/product.go @@ -0,0 +1,21 @@ +package ufs + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalufs "github.com/ucloud/ucloud-cli/products/ufs/internal/ufs" +) + +type product struct{} + +// New returns the ufs product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "ufs", Commands: []string{"ufs"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalufs.NewCommand(ctx)} +} diff --git a/products/ufs/product.yaml b/products/ufs/product.yaml new file mode 100644 index 0000000000..9653453d52 --- /dev/null +++ b/products/ufs/product.yaml @@ -0,0 +1,7 @@ +# products/ufs/product.yaml — ufs 产品元数据(归属真源,owner 自治维护) +name: ufs +owners: + - pearlinpan +commands: + - ufs +enabled: true diff --git a/products/ufs/testdata/cmdtree.golden b/products/ufs/testdata/cmdtree.golden new file mode 100644 index 0000000000..51a05eda54 --- /dev/null +++ b/products/ufs/testdata/cmdtree.golden @@ -0,0 +1,26 @@ +ucloud ufs use=ufs short=Manage UFS (UCloud File Storage) volumes +ucloud ufs create use=create short=Create a UFS volume + flag=charge-type short= default=Dynamic required= + flag=group short= default=Default required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=protocol-type short= default=NFS required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=remark short= default= required= + flag=size-gb short= default=100 required=true + flag=storage-type short= default=Basic required= + flag=zone short= default= required= +ucloud ufs delete use=delete short=Delete UFS volume(s) + flag=project-id short= default= required= + flag=region short= default= required= + flag=volume-id short= default=[] required=true + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud ufs describe use=describe short=Describe UFS volume(s) + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=volume-id short= default= required= + flag=zone short= default= required= diff --git a/products/ufs/testdata/completion.golden b/products/ufs/testdata/completion.golden new file mode 100644 index 0000000000..d8717824cf --- /dev/null +++ b/products/ufs/testdata/completion.golden @@ -0,0 +1,12 @@ +ucloud ufs create charge-type static Dynamic,Month,Trial,Year +ucloud ufs create project-id dynamic +ucloud ufs create protocol-type static NFS,SMB +ucloud ufs create region dynamic +ucloud ufs create storage-type static Advanced,Basic +ucloud ufs create zone dynamic +ucloud ufs delete project-id dynamic +ucloud ufs delete region dynamic +ucloud ufs delete zone dynamic +ucloud ufs describe project-id dynamic +ucloud ufs describe region dynamic +ucloud ufs describe zone dynamic diff --git a/products/upfs/internal/upfs/cmd.go b/products/upfs/internal/upfs/cmd.go new file mode 100644 index 0000000000..cb4c800aee --- /dev/null +++ b/products/upfs/internal/upfs/cmd.go @@ -0,0 +1,20 @@ +package upfs + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `upfs` root command and mounts the subcommands. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "upfs", + Short: "Manage UPFS (UCloud Parallel File Storage) volumes", + Long: "Manage UPFS (UCloud Parallel File Storage) volumes", + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDescribe(ctx)) + cmd.AddCommand(newDelete(ctx)) + return cmd +} diff --git a/products/upfs/internal/upfs/create.go b/products/upfs/internal/upfs/create.go new file mode 100644 index 0000000000..1bc20a62c1 --- /dev/null +++ b/products/upfs/internal/upfs/create.go @@ -0,0 +1,56 @@ +package upfs + +import ( + "fmt" + + "github.com/spf13/cobra" + + upfssdk "github.com/ucloud/ucloud-sdk-go/services/upfs" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud upfs create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, upfssdk.NewClient) + req := client.NewCreateUPFSVolumeRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a UPFS volume", + Long: "Create a UPFS volume", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + resp, err := client.CreateUPFSVolume(req) + if err != nil { + ctx.HandleError(err) + return + } + + text := fmt.Sprintf("upfs:%v created", resp.VolumeId) + fmt.Fprintln(w, text) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.VolumeId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VolumeName = flags.String("name", "", "Required. Name of the UPFS volume to create") + req.Size = flags.Int("size-gb", 500, "Required. Size of the UPFS volume. Unit: GB, must be a multiple of 100, minimum 500") + req.ProtocolType = flags.String("protocol-type", "POSIX", "Optional. Protocol type, currently only supports POSIX") + req.ChargeType = flags.String("charge-type", "Dynamic", "Optional. 'Year', pay yearly; 'Month', pay monthly; 'Dynamic', pay hourly") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months") + req.Tag = flags.String("group", "Default", "Optional. Business group") + req.Remark = flags.String("remark", "", "Optional. Remark") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic", "Trial") + command.SetFlagValues(cmd, "protocol-type", "POSIX") + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("size-gb") + + return cmd +} diff --git a/products/upfs/internal/upfs/delete.go b/products/upfs/internal/upfs/delete.go new file mode 100644 index 0000000000..bdad553f38 --- /dev/null +++ b/products/upfs/internal/upfs/delete.go @@ -0,0 +1,59 @@ +package upfs + +import ( + "fmt" + + "github.com/spf13/cobra" + + upfssdk "github.com/ucloud/ucloud-sdk-go/services/upfs" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud upfs delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes *bool + var volumeIDs *[]string + client := cli.NewServiceClient(ctx, upfssdk.NewClient) + req := client.NewRemoveUPFSVolumeRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete UPFS volume(s)", + Long: "Delete UPFS volume(s)", + Run: func(cmd *cobra.Command, args []string) { + ok, err := ctx.Confirm(*yes, "Are you sure to delete UPFS volume(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, id := range *volumeIDs { + id := ctx.PickResourceID(id) + req.VolumeId = &id + _, err := client.RemoveUPFSVolume(req) + if err != nil { + ctx.HandleError(err) + continue + } else { + fmt.Fprintf(w, "upfs[%s] deleted\n", *req.VolumeId) + results = append(results, cli.OpResultRow{ResourceID: *req.VolumeId, Action: "delete", Status: "Deleted"}) + } + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + volumeIDs = flags.StringSlice("volume-id", nil, "Required. The Resource ID of UPFS volumes to delete") + yes = flags.BoolP("yes", "y", false, "Optional. Do not prompt for confirmation.") + + ctx.BindCommonParams(cmd, req) + + cmd.MarkFlagRequired("volume-id") + + return cmd +} diff --git a/products/upfs/internal/upfs/describe.go b/products/upfs/internal/upfs/describe.go new file mode 100644 index 0000000000..fc9f779672 --- /dev/null +++ b/products/upfs/internal/upfs/describe.go @@ -0,0 +1,82 @@ +package upfs + +import ( + "fmt" + + "github.com/spf13/cobra" + + upfssdk "github.com/ucloud/ucloud-sdk-go/services/upfs" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDescribe ucloud upfs describe +func newDescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, upfssdk.NewClient) + req := client.NewDescribeUPFSVolumeRequest() + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe UPFS volume(s)", + Long: "Describe UPFS volume(s)", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.DescribeUPFSVolume(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []VolumeRow{} + for _, vol := range resp.DataSet { + row := VolumeRow{ + ResourceID: vol.VolumeId, + Name: vol.VolumeName, + Group: vol.Tag, + Size: fmt.Sprintf("%dGB", vol.Size), + ProtocolType: vol.ProtocolType, + MountAddress: vol.MountAddress, + ChargeType: vol.ChargeType, + State: vol.IsExpired, + CreationTime: common.FormatDate(vol.CreateTime), + Expiration: common.FormatDate(vol.ExpiredTime), + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VolumeId = flags.String("volume-id", "", "Optional. Resource ID of the UPFS volume") + req.Limit = flags.Int("limit", 50, "Optional. Limit") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + return cmd +} + +// describeUpfsByID returns the poller's describe func, closing over ctx so it +// can build an authed upfs client. +func describeUpfsByID(ctx *cli.Context) func(volumeID string, commonBase *request.CommonBase) (interface{}, error) { + return func(volumeID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, upfssdk.NewClient) + req := client.NewDescribeUPFSVolumeRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.VolumeId = &volumeID + limit := 50 + req.Limit = &limit + resp, err := client.DescribeUPFSVolume(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) < 1 { + return nil, nil + } + return &resp.DataSet[0], nil + } +} diff --git a/products/upfs/internal/upfs/rows.go b/products/upfs/internal/upfs/rows.go new file mode 100644 index 0000000000..0895e07812 --- /dev/null +++ b/products/upfs/internal/upfs/rows.go @@ -0,0 +1,15 @@ +package upfs + +// VolumeRow represents a single row in the upfs volume list output. +type VolumeRow struct { + ResourceID string + Name string + Group string + Size string + ProtocolType string + MountAddress string + ChargeType string + State string + CreationTime string + Expiration string +} diff --git a/products/upfs/internal/upfs/status.go b/products/upfs/internal/upfs/status.go new file mode 100644 index 0000000000..d15d9976a3 --- /dev/null +++ b/products/upfs/internal/upfs/status.go @@ -0,0 +1,8 @@ +package upfs + +// UPFS-domain state constants. +const ( + VOLUME_CREATING = "Creating" + VOLUME_AVAILABLE = "Available" + VOLUME_FAILED = "Failed" +) diff --git a/products/upfs/product.go b/products/upfs/product.go new file mode 100644 index 0000000000..36e6ed4dc5 --- /dev/null +++ b/products/upfs/product.go @@ -0,0 +1,21 @@ +package upfs + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalupfs "github.com/ucloud/ucloud-cli/products/upfs/internal/upfs" +) + +type product struct{} + +// New returns the upfs product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "upfs", Commands: []string{"upfs"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalupfs.NewCommand(ctx)} +} diff --git a/products/upfs/product.yaml b/products/upfs/product.yaml new file mode 100644 index 0000000000..c61f9f05e1 --- /dev/null +++ b/products/upfs/product.yaml @@ -0,0 +1,7 @@ +# products/upfs/product.yaml — upfs 产品元数据(归属真源,owner 自治维护) +name: upfs +owners: + - pearlinpan +commands: + - upfs +enabled: true diff --git a/products/upfs/testdata/cmdtree.golden b/products/upfs/testdata/cmdtree.golden new file mode 100644 index 0000000000..0267eaba7f --- /dev/null +++ b/products/upfs/testdata/cmdtree.golden @@ -0,0 +1,25 @@ +ucloud upfs use=upfs short=Manage UPFS (UCloud Parallel File Storage) volumes +ucloud upfs create use=create short=Create a UPFS volume + flag=charge-type short= default=Dynamic required= + flag=group short= default=Default required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=protocol-type short= default=POSIX required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=remark short= default= required= + flag=size-gb short= default=500 required=true + flag=zone short= default= required= +ucloud upfs delete use=delete short=Delete UPFS volume(s) + flag=project-id short= default= required= + flag=region short= default= required= + flag=volume-id short= default=[] required=true + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud upfs describe use=describe short=Describe UPFS volume(s) + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=volume-id short= default= required= + flag=zone short= default= required= diff --git a/products/upfs/testdata/completion.golden b/products/upfs/testdata/completion.golden new file mode 100644 index 0000000000..6740e8038b --- /dev/null +++ b/products/upfs/testdata/completion.golden @@ -0,0 +1,11 @@ +ucloud upfs create charge-type static Dynamic,Month,Trial,Year +ucloud upfs create project-id dynamic +ucloud upfs create protocol-type static POSIX +ucloud upfs create region dynamic +ucloud upfs create zone dynamic +ucloud upfs delete project-id dynamic +ucloud upfs delete region dynamic +ucloud upfs delete zone dynamic +ucloud upfs describe project-id dynamic +ucloud upfs describe region dynamic +ucloud upfs describe zone dynamic diff --git a/products/usnap/internal/usnap/cmd.go b/products/usnap/internal/usnap/cmd.go new file mode 100644 index 0000000000..4fa43cd8f7 --- /dev/null +++ b/products/usnap/internal/usnap/cmd.go @@ -0,0 +1,20 @@ +package usnap + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `usnap` root command and mounts the subcommands. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "usnap", + Short: "Manage USnap (UCloud Disk Snapshot Service)", + Long: "Manage USnap (UCloud Disk Snapshot Service)", + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDescribe(ctx)) + cmd.AddCommand(newDelete(ctx)) + return cmd +} diff --git a/products/usnap/internal/usnap/create.go b/products/usnap/internal/usnap/create.go new file mode 100644 index 0000000000..50e6e3fb87 --- /dev/null +++ b/products/usnap/internal/usnap/create.go @@ -0,0 +1,61 @@ +package usnap + +import ( + "fmt" + + "github.com/spf13/cobra" + + usnapsdk "github.com/ucloud/ucloud-sdk-go/services/usnap" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud usnap create +func newCreate(ctx *cli.Context) *cobra.Command { + var async *bool + client := cli.NewServiceClient(ctx, usnapsdk.NewClient) + req := client.NewCreateSnapshotServiceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a USnap snapshot service for a disk", + Long: "Create a USnap snapshot service for a disk", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + resp, err := client.CreateSnapshotService(req) + if err != nil { + ctx.HandleError(err) + return + } + + text := fmt.Sprintf("usnap:%v is creating", resp.SnapshotServiceId) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeUsnapByID(ctx)).Spoll(resp.SnapshotServiceId, text, []string{SERVICE_AVAILABLE, SERVICE_FAILED}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.SnapshotServiceId, Action: "create", Status: "Created"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VDiskId = flags.String("vdisk-id", "", "Required. Resource ID of the disk to create snapshot service for") + req.BackupMode = flags.String("backup-mode", "", "Optional. Backup mode") + req.Day = flags.Int("backup-day", 0, "Optional. Backup day range") + req.Hour = flags.Int("backup-hour", 0, "Optional. Backup hour") + req.Journal = flags.Int("journal", 0, "Optional. Journal retention count") + req.ChargeType = flags.String("charge-type", "Dynamic", "Optional. 'Year', pay yearly; 'Month', pay monthly; 'Dynamic', pay hourly") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months") + req.Tag = flags.String("group", "Default", "Optional. Business group") + async = flags.Bool("async", false, "Optional. Do not wait for the long-running operation to finish.") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic", "Trial") + + cmd.MarkFlagRequired("vdisk-id") + + return cmd +} diff --git a/products/usnap/internal/usnap/delete.go b/products/usnap/internal/usnap/delete.go new file mode 100644 index 0000000000..2d5d4a37a7 --- /dev/null +++ b/products/usnap/internal/usnap/delete.go @@ -0,0 +1,54 @@ +package usnap + +import ( + "fmt" + + "github.com/spf13/cobra" + + usnapsdk "github.com/ucloud/ucloud-sdk-go/services/usnap" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDelete ucloud usnap delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes *bool + client := cli.NewServiceClient(ctx, usnapsdk.NewClient) + req := client.NewDeleteSnapshotServiceRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete USnap snapshot service(s)", + Long: "Delete USnap snapshot service(s)", + Run: func(cmd *cobra.Command, args []string) { + ok, err := ctx.Confirm(*yes, "Are you sure to delete USnap snapshot service(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + + _, err = client.DeleteSnapshotService(req) + if err != nil { + ctx.HandleError(err) + } else { + fmt.Fprintf(w, "usnap[%s] deleted\n", *req.VDiskId) + results = append(results, cli.OpResultRow{ResourceID: *req.VDiskId, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.VDiskId = flags.String("vdisk-id", "", "Required. Resource ID of the disk whose snapshot service to delete") + yes = flags.BoolP("yes", "y", false, "Optional. Do not prompt for confirmation.") + + ctx.BindCommonParams(cmd, req) + + cmd.MarkFlagRequired("vdisk-id") + + return cmd +} diff --git a/products/usnap/internal/usnap/describe.go b/products/usnap/internal/usnap/describe.go new file mode 100644 index 0000000000..548318b020 --- /dev/null +++ b/products/usnap/internal/usnap/describe.go @@ -0,0 +1,86 @@ +package usnap + +import ( + "fmt" + + "github.com/spf13/cobra" + + usnapsdk "github.com/ucloud/ucloud-sdk-go/services/usnap" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDescribe ucloud usnap describe +func newDescribe(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, usnapsdk.NewClient) + req := client.NewDescribeSnapshotServiceRequest() + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe USnap snapshot service(s)", + Long: "Describe USnap snapshot service(s)", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.DescribeSnapshotService(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []SnapshotServiceRow{} + for _, svc := range resp.DataSet { + row := SnapshotServiceRow{ + ResourceID: svc.ServiceId, + VDiskID: svc.VDiskId, + VDiskName: svc.VDiskName, + VDiskSize: fmt.Sprintf("%dGB", svc.VDiskSize), + VDiskType: svc.VDiskType, + Group: svc.Tag, + ChargeType: svc.ChargeType, + AutoRenew: svc.AutoRenew, + Status: svc.Status, + Zone: svc.Zone, + CreationTime: common.FormatDate(svc.CreateTime), + Expiration: common.FormatDate(svc.ExpiredTime), + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.SnapshotServiceId = flags.String("service-id", "", "Optional. Resource ID of the snapshot service") + req.VDiskId = flags.String("vdisk-id", "", "Optional. Resource ID of the disk") + req.SnapshotId = flags.String("snapshot-id", "", "Optional. Resource ID of the snapshot") + req.Limit = flags.Int("limit", 50, "Optional. Limit") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindProjectID(cmd, req) + + return cmd +} + +// describeUsnapByID returns the poller's describe func, closing over ctx so it +// can build an authed usnap client. +func describeUsnapByID(ctx *cli.Context) func(serviceID string, commonBase *request.CommonBase) (interface{}, error) { + return func(serviceID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, usnapsdk.NewClient) + req := client.NewDescribeSnapshotServiceRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.SnapshotServiceId = &serviceID + limit := 50 + req.Limit = &limit + resp, err := client.DescribeSnapshotService(req) + if err != nil { + return nil, err + } + if len(resp.DataSet) < 1 { + return nil, nil + } + return &resp.DataSet[0], nil + } +} diff --git a/products/usnap/internal/usnap/rows.go b/products/usnap/internal/usnap/rows.go new file mode 100644 index 0000000000..e0feb84dda --- /dev/null +++ b/products/usnap/internal/usnap/rows.go @@ -0,0 +1,17 @@ +package usnap + +// SnapshotServiceRow represents a single row in the usnap snapshot service list output. +type SnapshotServiceRow struct { + ResourceID string + VDiskID string + VDiskName string + VDiskSize string + VDiskType string + Group string + ChargeType string + AutoRenew string + Status string + Zone string + CreationTime string + Expiration string +} diff --git a/products/usnap/internal/usnap/status.go b/products/usnap/internal/usnap/status.go new file mode 100644 index 0000000000..700658e9df --- /dev/null +++ b/products/usnap/internal/usnap/status.go @@ -0,0 +1,8 @@ +package usnap + +// USnap-domain state constants. +const ( + SERVICE_CREATING = "Creating" + SERVICE_AVAILABLE = "Available" + SERVICE_FAILED = "Failed" +) diff --git a/products/usnap/product.go b/products/usnap/product.go new file mode 100644 index 0000000000..89f5a30dd3 --- /dev/null +++ b/products/usnap/product.go @@ -0,0 +1,21 @@ +package usnap + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalusnap "github.com/ucloud/ucloud-cli/products/usnap/internal/usnap" +) + +type product struct{} + +// New returns the usnap product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "usnap", Commands: []string{"usnap"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalusnap.NewCommand(ctx)} +} diff --git a/products/usnap/product.yaml b/products/usnap/product.yaml new file mode 100644 index 0000000000..91e9528116 --- /dev/null +++ b/products/usnap/product.yaml @@ -0,0 +1,7 @@ +# products/usnap/product.yaml — usnap 产品元数据(归属真源,owner 自治维护) +name: usnap +owners: + - pearlinpan +commands: + - usnap +enabled: true diff --git a/products/usnap/testdata/cmdtree.golden b/products/usnap/testdata/cmdtree.golden new file mode 100644 index 0000000000..e426ffa0eb --- /dev/null +++ b/products/usnap/testdata/cmdtree.golden @@ -0,0 +1,29 @@ +ucloud usnap use=usnap short=Manage USnap (UCloud Disk Snapshot Service) +ucloud usnap create use=create short=Create a USnap snapshot service for a disk + flag=async short= default=false required= + flag=backup-day short= default=0 required= + flag=backup-hour short= default=0 required= + flag=backup-mode short= default= required= + flag=charge-type short= default=Dynamic required= + flag=group short= default=Default required= + flag=journal short= default=0 required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=vdisk-id short= default= required=true + flag=zone short= default= required= +ucloud usnap delete use=delete short=Delete USnap snapshot service(s) + flag=project-id short= default= required= + flag=region short= default= required= + flag=vdisk-id short= default= required=true + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud usnap describe use=describe short=Describe USnap snapshot service(s) + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required= + flag=snapshot-id short= default= required= + flag=vdisk-id short= default= required= + flag=zone short= default= required= diff --git a/products/usnap/testdata/completion.golden b/products/usnap/testdata/completion.golden new file mode 100644 index 0000000000..81b490fe24 --- /dev/null +++ b/products/usnap/testdata/completion.golden @@ -0,0 +1,10 @@ +ucloud usnap create charge-type static Dynamic,Month,Trial,Year +ucloud usnap create project-id dynamic +ucloud usnap create region dynamic +ucloud usnap create zone dynamic +ucloud usnap delete project-id dynamic +ucloud usnap delete region dynamic +ucloud usnap delete zone dynamic +ucloud usnap describe project-id dynamic +ucloud usnap describe region dynamic +ucloud usnap describe zone dynamic