diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 180d47f326..856e8b7c20 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -21,6 +21,7 @@ import ( "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/urocketmq" "github.com/ucloud/ucloud-cli/products/usnap" "github.com/ucloud/ucloud-cli/products/vpc" ) @@ -46,6 +47,7 @@ func registeredProducts() []cli.Product { umodelverse.New(), upfs.New(), uphost.New(), + urocketmq.New(), usnap.New(), vpc.New(), } diff --git a/products/urocketmq/internal/cmd.go b/products/urocketmq/internal/cmd.go new file mode 100644 index 0000000000..16c184b8e6 --- /dev/null +++ b/products/urocketmq/internal/cmd.go @@ -0,0 +1,27 @@ +package internal + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/group" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/message" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/token" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// NewCommand builds the top-level urocketmq command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "urocketmq", + Short: "Manage URocketMQ instances, topics, groups, tokens and messages", + Args: cobra.NoArgs, + } + cmd.AddCommand(service.NewCommand(ctx)) + cmd.AddCommand(topic.NewCommand(ctx)) + cmd.AddCommand(group.NewCommand(ctx)) + cmd.AddCommand(token.NewCommand(ctx)) + cmd.AddCommand(message.NewCommand(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/group/cmd.go b/products/urocketmq/internal/group/cmd.go new file mode 100644 index 0000000000..0c91499b46 --- /dev/null +++ b/products/urocketmq/internal/group/cmd.go @@ -0,0 +1,21 @@ +package group + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `urocketmq group` resource-group command. Action subcommands are appended +// at the end in subsequent batches (order is fixed, golden depends). +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "group", + Short: "Manage URocketMQ consumer groups", + Args: cobra.NoArgs, + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newList(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/group/completion.go b/products/urocketmq/internal/group/completion.go new file mode 100644 index 0000000000..718509579d --- /dev/null +++ b/products/urocketmq/internal/group/completion.go @@ -0,0 +1,33 @@ +package group + +import ( + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" +) + +// GroupList returns group names for the given service. Exported for self-use (delete --group-name completion) +// and reused by sibling list; cross-group one-way imports service, same-group calls directly. +func GroupList(ctx *cli.Context, projectID, region, serviceID string) []string { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQGroupRequest() + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + req.ServiceId = sdk.String(serviceID) + names := make([]string, 0) + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.ListURocketMQGroup(req) + if err != nil { + return nil + } + for _, g := range resp.GroupList { + names = append(names, g.GroupName) + } + if offset+limit >= resp.TotalCount { + break + } + } + return names +} diff --git a/products/urocketmq/internal/group/create.go b/products/urocketmq/internal/group/create.go new file mode 100644 index 0000000000..065be2071c --- /dev/null +++ b/products/urocketmq/internal/group/create.go @@ -0,0 +1,50 @@ +package group + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newCreate ucloud urocketmq group create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewCreateURocketMQGroupRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a consumer group", + Long: "Create a consumer group", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := client.CreateURocketMQGroup(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ + ResourceID: resp.GroupId, + Action: "create", + Status: "Created", + }) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.Name = cmd.Flags().String("name", "", "Required. Consumer group name, 1-36 characters, supports letters, digits, - and _") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.Remark = cmd.Flags().String("remark", "", "Optional. Group remark") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("service-id") + + return cmd +} diff --git a/products/urocketmq/internal/group/delete.go b/products/urocketmq/internal/group/delete.go new file mode 100644 index 0000000000..a05bc31cf8 --- /dev/null +++ b/products/urocketmq/internal/group/delete.go @@ -0,0 +1,63 @@ +package group + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newDelete ucloud urocketmq group delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewDeleteURocketMQGroupRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a consumer group", + Long: "Delete a consumer group", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete group %q?", *req.GroupName)) + if err != nil { + return err + } + if !ok { + return nil + } + _, err = client.DeleteURocketMQGroup(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ + ResourceID: *req.GroupName, + Action: "delete", + Status: "Deleted", + }) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.GroupName = cmd.Flags().String("group-name", "", "Required. Consumer group name") + cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "group-name", func() []string { + return GroupList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("group-name") + + return cmd +} diff --git a/products/urocketmq/internal/group/list.go b/products/urocketmq/internal/group/list.go new file mode 100644 index 0000000000..21e61b2fc5 --- /dev/null +++ b/products/urocketmq/internal/group/list.go @@ -0,0 +1,74 @@ +package group + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newList ucloud urocketmq group list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQGroupRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List URocketMQ consumer groups", + Long: "List URocketMQ consumer groups", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.ListURocketMQGroup(req) + if err != nil { + ctx.HandleError(err) + return + } + listGroup(ctx, resp.GroupList) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.Limit = cmd.Flags().Int("limit", 50, "Optional. Limit default 50") + req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset default 0") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("service-id") + + return cmd +} + +// listGroup renders the group list. json/yaml emits full-field groupRow; table mode uses groupRowDefault. +func listGroup(ctx *cli.Context, groups []urocketmq.GroupBaseInfo) { + list := make([]groupRow, 0, len(groups)) + for _, g := range groups { + list = append(list, groupRow{ + GroupName: g.GroupName, + Id: g.Id, + Remark: g.Remark, + CreateTime: g.CreateTime, + }) + } + + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return + } + + rows := make([]groupRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, groupRowDefault{ + GroupName: r.GroupName, + Id: r.Id, + Remark: r.Remark, + CreateTime: common.FormatDate(r.CreateTime), + }) + } + ctx.PrintList(rows) +} diff --git a/products/urocketmq/internal/group/rows.go b/products/urocketmq/internal/group/rows.go new file mode 100644 index 0000000000..87e8f77f79 --- /dev/null +++ b/products/urocketmq/internal/group/rows.go @@ -0,0 +1,17 @@ +package group + +// groupRow is the full-field row (json/yaml mode). +type groupRow struct { + GroupName string + Id string + Remark string + CreateTime int +} + +// groupRowDefault is the default curated columns in table mode: GroupName, Id, Remark, CreateTime. +type groupRowDefault struct { + GroupName string + Id string + Remark string + CreateTime string +} diff --git a/products/urocketmq/internal/message/cmd.go b/products/urocketmq/internal/message/cmd.go new file mode 100644 index 0000000000..229c340790 --- /dev/null +++ b/products/urocketmq/internal/message/cmd.go @@ -0,0 +1,21 @@ +package message + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `urocketmq message` resource-group command. Action subcommands are appended +// at the end in subsequent batches (order is fixed, golden depends). +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "message", + Short: "Query URocketMQ messages", + Args: cobra.NoArgs, + } + cmd.AddCommand(newQueryByID(ctx)) + cmd.AddCommand(newQueryByKey(ctx)) + cmd.AddCommand(newQueryByTopic(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/message/query_by_id.go b/products/urocketmq/internal/message/query_by_id.go new file mode 100644 index 0000000000..95728663b4 --- /dev/null +++ b/products/urocketmq/internal/message/query_by_id.go @@ -0,0 +1,52 @@ +package message + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// newQueryByID ucloud urocketmq message query-by-id +func newQueryByID(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewQueryURocketMQMessageByIDRequest() + cmd := &cobra.Command{ + Use: "query-by-id", + Short: "Query a message by ID", + Long: `Query a message by ID, returns the full message detail including body.`, + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.QueryURocketMQMessageByID(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := toMessageDetailRows(resp.MessageList) + printMessageRows(ctx, rows) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + req.MsgId = cmd.Flags().String("msg-id", "", "Required. Message ID to query") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.TopicName = cmd.Flags().String("topic-name", "", "Required. Topic name") + _ = cmd.MarkFlagRequired("msg-id") + _ = cmd.MarkFlagRequired("service-id") + _ = cmd.MarkFlagRequired("topic-name") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "topic-name", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + + return cmd +} diff --git a/products/urocketmq/internal/message/query_by_key.go b/products/urocketmq/internal/message/query_by_key.go new file mode 100644 index 0000000000..c36caeeae4 --- /dev/null +++ b/products/urocketmq/internal/message/query_by_key.go @@ -0,0 +1,72 @@ +package message + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// newQueryByKey ucloud urocketmq message query-by-key +func newQueryByKey(ctx *cli.Context) *cobra.Command { + var idOnly bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewQueryURocketMQMessageByKeyRequest() + cmd := &cobra.Command{ + Use: "query-by-key", + Short: "Query messages by key", + Long: `Query messages by a custom message key under a specific topic.`, + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.QueryURocketMQMessageByKey(req) + if err != nil { + ctx.HandleError(err) + return + } + if idOnly { + listMessageID(ctx, resp.MessageList) + return + } + rows := toMessageBaseInfoRows(resp.MessageList) + printMessageRows(ctx, rows) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + req.Key = cmd.Flags().String("key", "", "Required. Message key to query") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.TopicName = cmd.Flags().String("topic-name", "", "Required. Topic name") + cmd.Flags().BoolVar(&idOnly, "id-only", false, "Optional. Only display message IDs") + _ = cmd.MarkFlagRequired("key") + _ = cmd.MarkFlagRequired("service-id") + _ = cmd.MarkFlagRequired("topic-name") + + command.SetFlagValues(cmd, "id-only", "true", "false") + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "topic-name", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + + return cmd +} + +// listMessageID outputs a message ID list (--id-only mode), writing to ctx.Out() to avoid capture by +// ProgressWriter which would yield empty output in non-TTY mode. +func listMessageID(ctx *cli.Context, infos []urocketmq.MessageBaseInfo) { + ids := make([]string, 0, len(infos)) + for _, info := range infos { + ids = append(ids, info.MsgId) + } + fmt.Fprintln(ctx.Out(), strings.Join(ids, ",")) +} diff --git a/products/urocketmq/internal/message/query_by_topic.go b/products/urocketmq/internal/message/query_by_topic.go new file mode 100644 index 0000000000..57976f0783 --- /dev/null +++ b/products/urocketmq/internal/message/query_by_topic.go @@ -0,0 +1,74 @@ +package message + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// newQueryByTopic ucloud urocketmq message query-by-topic +func newQueryByTopic(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewQueryURocketMQMessageByTopicRequest() + cmd := &cobra.Command{ + Use: "query-by-topic", + Short: "Query messages by topic and time range", + Long: `Query messages by topic and time range. Time format: {2006-01-02 15:04:05}`, + Run: func(cmd *cobra.Command, args []string) { + beginStr, _ := cmd.Flags().GetString("begin") + endStr, _ := cmd.Flags().GetString("end") + const layout = "2006-01-02 15:04:05" + beginTime, err := time.ParseInLocation(layout, beginStr, time.Local) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid begin time %q, expected format: 2006-01-02 15:04:05", beginStr)) + return + } + endTime, err := time.ParseInLocation(layout, endStr, time.Local) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid end time %q, expected format: 2006-01-02 15:04:05", endStr)) + return + } + beginUnix := int(beginTime.Unix()) + endUnix := int(endTime.Unix()) + req.Begin = &beginUnix + req.End = &endUnix + resp, err := client.QueryURocketMQMessageByTopic(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := toMessageBaseInfoRows(resp.MessageList) + printMessageRows(ctx, rows) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + cmd.Flags().String("begin", "", "Required. Begin time (YYYY-MM-DD HH:MM:SS)") + cmd.Flags().String("end", "", "Required. End time (YYYY-MM-DD HH:MM:SS)") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.TopicName = cmd.Flags().String("topic-name", "", "Required. Topic name") + _ = cmd.MarkFlagRequired("begin") + _ = cmd.MarkFlagRequired("end") + _ = cmd.MarkFlagRequired("service-id") + _ = cmd.MarkFlagRequired("topic-name") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "topic-name", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + + return cmd +} diff --git a/products/urocketmq/internal/message/rows.go b/products/urocketmq/internal/message/rows.go new file mode 100644 index 0000000000..c718878062 --- /dev/null +++ b/products/urocketmq/internal/message/rows.go @@ -0,0 +1,82 @@ +package message + +import ( + "github.com/ucloud/ucloud-cli/internal/common" + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// messageRow is the full-field row (json/yaml mode). Mapped from SDK MessageDetail/MessageBaseInfo. +// query-by-key and query-by-topic return MessageBaseInfo (no Body); Body field is empty. +type messageRow struct { + MsgId string + Key string + Tag string + StoreTime string + Topic string + Body string +} + +// messageRowDefault is the default curated columns in table mode. +type messageRowDefault struct { + MsgId string + Key string + Tag string + StoreTime string + Topic string +} + +// formatStoreTime formats a Unix millisecond timestamp as a date-time string. +func formatStoreTime(ms int) string { + if ms == 0 { + return "" + } + return common.FormatDateTime(ms / 1000) +} + +// toMessageDetailRows converts a SDK MessageDetail slice to a messageRow slice. +func toMessageDetailRows(details []urocketmq.MessageDetail) []messageRow { + rows := make([]messageRow, 0, len(details)) + for _, d := range details { + rows = append(rows, messageRow{ + MsgId: d.MsgId, + Key: d.Properties.KEYS, + Tag: d.Properties.TAGS, + StoreTime: formatStoreTime(d.StoreTimestamp), + Topic: d.Topic, + Body: d.MessageBody, + }) + } + return rows +} + +// toMessageBaseInfoRows converts a SDK MessageBaseInfo slice to a messageRow slice. +func toMessageBaseInfoRows(infos []urocketmq.MessageBaseInfo) []messageRow { + rows := make([]messageRow, 0, len(infos)) + for _, info := range infos { + rows = append(rows, messageRow{ + MsgId: info.MsgId, + Key: info.Properties.KEYS, + Tag: info.Properties.TAGS, + StoreTime: formatStoreTime(info.StoreTimestamp), + Topic: info.Topic, + }) + } + return rows +} + +// printMessageRows routes output by ctx.Format(). json/yaml prints full fields, table prints curated columns. +func printMessageRows(ctx *cli.Context, rows []messageRow) { + if ctx.Format() != cli.OutputTable { + ctx.PrintList(rows) + return + } + defaultRows := make([]messageRowDefault, 0, len(rows)) + for _, r := range rows { + defaultRows = append(defaultRows, messageRowDefault{ + MsgId: r.MsgId, Key: r.Key, Tag: r.Tag, StoreTime: r.StoreTime, Topic: r.Topic, + }) + } + ctx.PrintList(defaultRows) +} diff --git a/products/urocketmq/internal/service/cmd.go b/products/urocketmq/internal/service/cmd.go new file mode 100644 index 0000000000..fe9040f6a8 --- /dev/null +++ b/products/urocketmq/internal/service/cmd.go @@ -0,0 +1,25 @@ +package service + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `urocketmq service` resource-group command. Action subcommands are appended +// at the end (order is fixed, golden depends); +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "service", + Short: "Manage URocketMQ service instances", + Args: cobra.NoArgs, + } + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newGet(ctx)) + cmd.AddCommand(newUpdateName(ctx)) + cmd.AddCommand(newUpdateRemark(ctx)) + cmd.AddCommand(newPrice(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/service/completion.go b/products/urocketmq/internal/service/completion.go new file mode 100644 index 0000000000..4842893193 --- /dev/null +++ b/products/urocketmq/internal/service/completion.go @@ -0,0 +1,76 @@ +package service + +import ( + "fmt" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" +) + +// ServiceList returns ServiceIds for the given project/region. Exported for --service-id completion +// reuse by topic/group/token/message (one-way import of service package to avoid circular deps). +func ServiceList(ctx *cli.Context, projectID, region string) []string { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQServiceRequest() + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + req.Limit = sdk.Int(1000) + req.Offset = sdk.Int(0) + resp, err := client.ListURocketMQService(req) + if err != nil { + return nil + } + ids := make([]string, 0, len(resp.ServiceList)) + for _, s := range resp.ServiceList { + ids = append(ids, s.ServiceId) + } + return ids +} + +// getAllVPCIdNames returns "VPCId/Name" completion candidates (--vpc-id completion). See uhost completion.go. +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeVPCRequest() + req.ProjectId = &project + req.Region = ®ion + resp, err := client.DescribeVPC(req) + if err != nil { + return nil + } + list := make([]string, 0, len(resp.DataSet)) + for _, v := range resp.DataSet { + list = append(list, fmt.Sprintf("%s/%s", v.VPCId, v.Name)) + } + return list +} + +// getAllSubnetIDNames returns "SubnetId/Name" completion candidates (--subnet-id completion). See uhost completion.go. +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := make([]vpc.SubnetInfo, 0) + for limit, offset := 50, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.DescribeSubnet(req) + if err != nil { + return nil + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + list := make([]string, 0, len(subnets)) + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} diff --git a/products/urocketmq/internal/service/create.go b/products/urocketmq/internal/service/create.go new file mode 100644 index 0000000000..f7bc6bceab --- /dev/null +++ b/products/urocketmq/internal/service/create.go @@ -0,0 +1,121 @@ +package service + +import ( + "fmt" + + "github.com/spf13/cobra" + + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud urocketmq service create +func newCreate(ctx *cli.Context) *cobra.Command { + var async bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewCreateURocketMQServiceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a URocketMQ service instance", + Long: "Create a URocketMQ service instance", + RunE: func(cmd *cobra.Command, args []string) error { + req.VPCId = sdk.String(ctx.PickResourceID(*req.VPCId)) + req.SubnetId = sdk.String(ctx.PickResourceID(*req.SubnetId)) + if *req.Storage <= 0 || *req.Storage%100 != 0 { + return fmt.Errorf("--storage-gb must be a positive multiple of 100") + } + if sdk.StringValue(req.ChargeType) == "Dynamic" { + req.Quantity = sdk.Int(0) + } + resp, err := client.CreateURocketMQService(req) + if err != nil { + return err + } + + serviceID := resp.ServiceId + prog := ctx.NewProgress() + block := prog.NewBlock() + ctx.EmitResult(cli.OpResultRow{ResourceID: serviceID, Action: "create", Status: "Initializing"}) + + text := fmt.Sprintf("the service[%s] is initializing", serviceID) + if async { + block.Append(text) + } else { + prog.Sspoll(describeServiceByID(ctx), serviceID, text, + []string{SERVICE_AVAILABLE, SERVICE_CREATE_FAILED}, block, &req.CommonBase) + } + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ChargeType = flags.String("charge-type", "Month", "Required. Charge type. Enum: Year, Month, Dynamic") + req.Edition = flags.String("edition", "Enterprise", "Required. Edition. Unique value: Enterprise") + req.Mode = flags.String("mode", "PrivateNet", "Required. Network mode. Unique value: PrivateNet") + req.Name = flags.String("name", "", "Required. Service name. Regex: ^[a-zA-Z0-9-_]{1,36}$") + req.PublicVersion = flags.String("public-version", "", "Required. Cluster version. Enum: v4, v5. Note: each region currently supports only one version, confirm with region") + req.Storage = flags.Int("storage-gb", 0, "Required. Message storage space in GB. Range depends on TPS") + req.SubnetId = flags.String("subnet-id", "", "Required. Subnet ID. Default to current region's default subnet") + req.Tps = flags.String("tps", "", "Required. Transactions per second. Enum: 10000, 20000, 50000, 100000, 200000. Note: v4 supports 20000, 50000, 100000, 200000; v5 currently supports only 10000, 20000.") + req.VPCId = flags.String("vpc-id", "", "Required. VPC ID. Default to current region's default VPC") + req.FileReservedTime = flags.String("file-reserved-time", "3", "Optional. Message reserved time in days, default 3") + req.Quantity = flags.Int("quantity", 1, "Optional. Purchase duration in months. Month: 1-9(month), 0=until end of current month; Dynamic: ignore; Year: use --quantity as years") + req.Remark = flags.String("remark", "", "Optional. Remark") + req.Tag = flags.String("group", "Default", "Optional. Business group tag") + + flags.BoolVar(&async, "async", false, "Optional. Do not wait for the long-running operation to finish.") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Dynamic") + command.SetFlagValues(cmd, "edition", "Enterprise") + command.SetFlagValues(cmd, "mode", "PrivateNet") + command.SetFlagValues(cmd, "public-version", "v4", "v5") + command.SetFlagValues(cmd, "tps", "10000", "20000", "50000", "100000", "200000") + + command.SetCompletion(cmd, "vpc-id", func() []string { + return getAllVPCIdNames(ctx, req.GetProjectId(), req.GetRegion()) + }) + command.SetCompletion(cmd, "subnet-id", func() []string { + return getAllSubnetIDNames(ctx, *req.VPCId, req.GetProjectId(), req.GetRegion()) + }) + + cmd.MarkFlagRequired("charge-type") + cmd.MarkFlagRequired("edition") + cmd.MarkFlagRequired("mode") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("public-version") + cmd.MarkFlagRequired("storage-gb") + cmd.MarkFlagRequired("subnet-id") + cmd.MarkFlagRequired("tps") + cmd.MarkFlagRequired("vpc-id") + + return cmd +} + +// describeServiceByID returns the describe function used by Sspoll, polling via GetURocketMQService +func describeServiceByID(ctx *cli.Context) func(string, *request.CommonBase) (interface{}, error) { + return func(id string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewGetURocketMQServiceRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.ServiceId = sdk.String(id) + resp, err := client.GetURocketMQService(req) + if err != nil { + return nil, err + } + if len(resp.ServiceList) < 1 { + return nil, nil + } + return &resp.ServiceList[0], nil + } +} diff --git a/products/urocketmq/internal/service/delete.go b/products/urocketmq/internal/service/delete.go new file mode 100644 index 0000000000..79de2a5cd6 --- /dev/null +++ b/products/urocketmq/internal/service/delete.go @@ -0,0 +1,94 @@ +package service + +import ( + "fmt" + + "github.com/spf13/cobra" + + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete ucloud urocketmq service delete +func newDelete(ctx *cli.Context) *cobra.Command { + var async bool + var yes bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewDeleteURocketMQServiceRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a URocketMQ service instance", + Long: "Delete a URocketMQ service instance", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + serviceID := *req.ServiceId + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete service %q?", serviceID)) + if err != nil { + return err + } + if !ok { + return nil + } + + resp, err := client.DeleteURocketMQService(req) + if err != nil { + if serr, ok := err.(uerr.ServerError); ok && serr.Code() == 99539 { + return fmt.Errorf("please delete all Topics and Groups under the instance before deleting it") + } + return err + } + + prog := ctx.NewProgress() + block := prog.NewBlock() + ctx.EmitResult(cli.OpResultRow{ResourceID: serviceID, Action: "delete", Status: "Deleting"}) + + _ = resp // delete response contains only Message + + text := fmt.Sprintf("the service[%s] is deleting", serviceID) + if async { + block.Append(text) + } else { + prog.Sspoll(describeDeletedServiceByID(ctx), serviceID, text, + // Poll until service no longer exists (describe returns nil) or deletion fails. + []string{SERVICE_DELETED, SERVICE_DELETE_FAILED}, block, &req.CommonBase) + } + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ServiceId = flags.String("service-id", "", "Required. Service ID") + flags.BoolVar(&async, "async", false, "Optional. Do not wait for the long-running operation to finish.") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetCompletion(cmd, "service-id", func() []string { + return ServiceList(ctx, req.GetProjectId(), req.GetRegion()) + }) + + cmd.MarkFlagRequired("service-id") + + return cmd +} + +func describeDeletedServiceByID(ctx *cli.Context) func(string, *request.CommonBase) (interface{}, error) { + describe := describeServiceByID(ctx) + return func(id string, commonBase *request.CommonBase) (interface{}, error) { + inst, err := describe(id, commonBase) + if err != nil { + return nil, err + } + if inst == nil { + return &urocketmq.ServiceDetail{State: SERVICE_DELETED}, nil + } + return inst, nil + } +} diff --git a/products/urocketmq/internal/service/get.go b/products/urocketmq/internal/service/get.go new file mode 100644 index 0000000000..812e5449df --- /dev/null +++ b/products/urocketmq/internal/service/get.go @@ -0,0 +1,45 @@ +package service + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newGet ucloud urocketmq service get +func newGet(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewGetURocketMQServiceRequest() + cmd := &cobra.Command{ + Use: "get", + Short: "Get details of a URocketMQ service instance", + Long: "Get details of a URocketMQ service instance", + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := client.GetURocketMQService(req) + if err != nil { + return err + } + ctx.PrintList(resp.ServiceList) + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ServiceId = flags.String("service-id", "", "Required. Service ID") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetCompletion(cmd, "service-id", func() []string { + return ServiceList(ctx, req.GetProjectId(), req.GetRegion()) + }) + + cobra.CheckErr(cmd.MarkFlagRequired("service-id")) + + return cmd +} diff --git a/products/urocketmq/internal/service/list.go b/products/urocketmq/internal/service/list.go new file mode 100644 index 0000000000..c9cac40635 --- /dev/null +++ b/products/urocketmq/internal/service/list.go @@ -0,0 +1,190 @@ +package service + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + sdkerror "github.com/ucloud/ucloud-sdk-go/ucloud/error" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newList ucloud urocketmq service list +func newList(ctx *cli.Context) *cobra.Command { + var allRegion, idOnly bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQServiceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List all URocketMQ instances", + Long: "List all URocketMQ instances", + Run: func(cmd *cobra.Command, args []string) { + services, err := getAllServices(ctx, client, req, allRegion) + if err != nil { + ctx.HandleError(err) + return + } + if idOnly { + listServiceID(ctx, services) + } else { + listService(ctx, services, allRegion) + } + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.Limit = cmd.Flags().Int("limit", 20, "Optional. Limit default 20, max value 1000") + req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset default 0") + cmd.Flags().BoolVar(&allRegion, "all-region", false, "Optional. Accept values: true or false. List URocketMQ instances of all regions when assigned true") + cmd.Flags().BoolVar(&idOnly, "id-only", false, "Optional. Just display resource id of URocketMQ service") + + command.SetFlagValues(cmd, "all-region", "true", "false") + command.SetFlagValues(cmd, "id-only", "true", "false") + + return cmd +} + +// getAllServices handles --all-region cross-region aggregation; single region fetches one page by user limit/offset. +func getAllServices(ctx *cli.Context, client *urocketmq.URocketMQClient, req *urocketmq.ListURocketMQServiceRequest, allRegion bool) ([]urocketmq.ServiceBaseInfo, error) { + if allRegion { + result := make([]urocketmq.ServiceBaseInfo, 0) + regions, err := ctx.AllRegions() + if err != nil { + return nil, err + } + for _, region := range regions { + _req := *req + _req.Region = sdk.String(region) + // --all-region does not paginate, fetches all per region + services, err := fetchServicesPageOff(client, &_req) + // Some accounts lack URocketMQ permissions in the current region; skip per platform convention RetCode 230 + if e, ok := err.(sdkerror.Error); ok && e.Code() == _RetCodeRegionNoPermission { + continue + } + if err != nil { + return nil, err + } + result = append(result, services...) + } + return result, nil + } + + resp, err := client.ListURocketMQService(req) + if err != nil { + return nil, err + } + return resp.ServiceList, nil +} + +// fetchServicesPageOff paginates all URocketMQ instances in the specified region. SDK response has no +// TotalCount, so uses last page item count < pageSize as termination condition. +func fetchServicesPageOff(client *urocketmq.URocketMQClient, req *urocketmq.ListURocketMQServiceRequest) ([]urocketmq.ServiceBaseInfo, error) { + _req := *req + result := make([]urocketmq.ServiceBaseInfo, 0) + for limit, offset := 100, 0; ; offset += limit { + _req.Offset = sdk.Int(offset) + _req.Limit = sdk.Int(limit) + resp, err := client.ListURocketMQService(&_req) + if err != nil { + return nil, err + } + result = append(result, resp.ServiceList...) + if len(resp.ServiceList) < limit { + break + } + } + return result, nil +} + +// listService renders the service list. json/yaml emits full-field serviceRow; table mode uses curated +// columns (serviceRowDefault, serviceRowAllRegion for --all-region with Region appended). +func listService(ctx *cli.Context, services []urocketmq.ServiceBaseInfo, listAllRegion bool) { + list := make([]serviceRow, 0, len(services)) + for _, s := range services { + list = append(list, toServiceRow(s)) + } + + // JSON/YAML mode: emits full-field rows (like uhost, --json always marshals full fields). + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return + } + + if listAllRegion { + rows := make([]serviceRowAllRegion, 0, len(list)) + for _, r := range list { + rows = append(rows, serviceRowAllRegion{ + Name: r.Name, ServiceId: r.ServiceId, State: r.State, + Config: formatServiceConfig(r.Tps, r.Storage), Address: r.Address, + CreateTime: common.FormatDate(r.CreateTime), ExpireTime: common.FormatDate(r.ExpireTime), + Region: r.Region, + }) + } + ctx.PrintList(rows) + return + } + + rows := make([]serviceRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, serviceRowDefault{ + Name: r.Name, ServiceId: r.ServiceId, State: r.State, + Config: formatServiceConfig(r.Tps, r.Storage), Address: r.Address, + CreateTime: common.FormatDate(r.CreateTime), ExpireTime: common.FormatDate(r.ExpireTime), + }) + } + ctx.PrintList(rows) +} + +// toServiceRow maps SDK ServiceBaseInfo to a full-field row. +func toServiceRow(s urocketmq.ServiceBaseInfo) serviceRow { + return serviceRow{ + ServiceId: s.ServiceId, + Name: s.Name, + State: s.State, + Tps: s.Tps, + Storage: s.Storage, + TopicLimit: s.TopicLimit, + Address: s.Address, + AddressExtranet: s.AddressExtranet, + VpcId: s.VpcId, + SubnetId: s.SubnetId, + ChargeType: s.ChargeType, + CreateTime: s.CreateTime, + ExpireTime: s.ExpireTime, + Remark: s.Remark, + Tag: s.Tag, + Edition: s.Edition, + Mode: s.Mode, + AutoRenew: s.AutoRenew, + IsExpire: s.IsExpire, + Quantity: s.Quantity, + Region: s.Region, + } +} + +// formatServiceConfig concatenates the Config column in table mode: Tps + Storage. +func formatServiceConfig(tps, storage int) string { + return fmt.Sprintf("tps:%d storage:%dG", tps, storage) +} + +// listServiceID outputs only the ServiceId list to ctx.Out() (not ProgressWriter) for script capture. +// Corresponds to uhost listUhostID. +func listServiceID(ctx *cli.Context, services []urocketmq.ServiceBaseInfo) { + ids := make([]string, 0, len(services)) + for _, s := range services { + ids = append(ids, s.ServiceId) + } + fmt.Fprintln(ctx.Out(), strings.Join(ids, ",")) +} + +// _RetCodeRegionNoPermission is the SDK RetCode when account lacks permission in the current region; +// --all-region path skips that region. Follows uhost (cmd/uhost.go) platform convention. +const _RetCodeRegionNoPermission = 230 diff --git a/products/urocketmq/internal/service/price.go b/products/urocketmq/internal/service/price.go new file mode 100644 index 0000000000..a6ec4150cd --- /dev/null +++ b/products/urocketmq/internal/service/price.go @@ -0,0 +1,83 @@ +package service + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPrice ucloud urocketmq service price +func newPrice(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewGetURocketMQServicePriceRequest() + cmd := &cobra.Command{ + Use: "price", + Short: "Get price of URocketMQ service instance", + Long: "Get price of URocketMQ service instance", + RunE: func(cmd *cobra.Command, args []string) error { + if *req.Storage <= 0 || *req.Storage%100 != 0 { + return fmt.Errorf("--storage-gb must be a positive multiple of 100") + } + if sdk.StringValue(req.ChargeType) == "Dynamic" { + req.Quantity = sdk.Int(0) + } + resp, err := client.GetURocketMQServicePrice(req) + if err != nil { + return err + } + list := make([]urocketmq.PriceSet, 0, len(resp.PriceSet)) + for _, p := range resp.PriceSet { + list = append(list, p) + } + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return nil + } + rows := make([]priceRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, priceRowDefault{ + ChargeName: r.ChargeName, + ChargeType: r.ChargeType, + Price: r.Price, + }) + } + ctx.PrintList(rows) + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ChargeType = flags.String("charge-type", "Month", "Required. Charge type. Enum: Year, Month, Dynamic") + req.Edition = flags.String("edition", "Enterprise", "Required. Edition. Unique value: Enterprise") + req.Mode = flags.String("mode", "PrivateNet", "Required. Network mode. Unique value: PrivateNet") + req.PublicVersion = flags.String("public-version", "", "Required. Cluster version. Enum: v4, v5. Note: each region currently supports only one version, confirm with region") + req.Quantity = flags.Int("quantity", 1, "Optional. Purchase duration in months. Month: 1-9(month), 0=until end of current month; Dynamic: ignore; Year: use --quantity as years") + req.Storage = flags.Int("storage-gb", 0, "Required. Message storage space in GB") + req.TPS = flags.Int("tps", 0, "Required. Transactions per second. Enum: 10000, 20000, 50000, 100000, 200000. Note: v4 supports 20000, 50000, 100000, 200000; v5 currently supports only 10000, 20000.") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Dynamic") + command.SetFlagValues(cmd, "edition", "Enterprise") + command.SetFlagValues(cmd, "mode", "PrivateNet") + command.SetFlagValues(cmd, "public-version", "v4", "v5") + command.SetFlagValues(cmd, "tps", "10000", "20000", "50000", "100000", "200000") + + cmd.MarkFlagRequired("charge-type") + cmd.MarkFlagRequired("edition") + cmd.MarkFlagRequired("mode") + cmd.MarkFlagRequired("public-version") + cmd.MarkFlagRequired("storage-gb") + cmd.MarkFlagRequired("tps") + + return cmd +} diff --git a/products/urocketmq/internal/service/rows.go b/products/urocketmq/internal/service/rows.go new file mode 100644 index 0000000000..2ae812de31 --- /dev/null +++ b/products/urocketmq/internal/service/rows.go @@ -0,0 +1,52 @@ +package service + +type serviceRow struct { + ServiceId string + Name string + State string + Tps int + Storage int + TopicLimit int + Address string + AddressExtranet string + VpcId string + SubnetId string + ChargeType string + CreateTime int + ExpireTime int + Remark string + Tag string + Edition string + Mode string + AutoRenew string + IsExpire string + Quantity int + Region string +} + +type serviceRowDefault struct { + Name string + ServiceId string + State string + Config string + Address string + CreateTime string + ExpireTime string +} + +type serviceRowAllRegion struct { + Name string + ServiceId string + State string + Config string + Address string + CreateTime string + ExpireTime string + Region string +} + +type priceRowDefault struct { + ChargeName string + ChargeType string + Price float64 +} diff --git a/products/urocketmq/internal/service/status.go b/products/urocketmq/internal/service/status.go new file mode 100644 index 0000000000..1c8248814b --- /dev/null +++ b/products/urocketmq/internal/service/status.go @@ -0,0 +1,21 @@ +package service + +// Service status constants, from ServiceBaseInfo.State / ServiceDetail.State enum values. +// See uhost status.go (HOST_RUNNING / HOST_STOPPED / HOST_FAIL). +const ( + SERVICE_AVAILABLE = "Available" + SERVICE_INITIALIZING = "Initializing" + SERVICE_DELETING = "Deleting" + SERVICE_CREATE_FAILED = "CreateFailed" + SERVICE_CLOSING = "Closing" + SERVICE_CLOSED = "Closed" + SERVICE_CLOSE_FAILED = "CloseFailed" + SERVICE_RECOVERING = "Recovering" + SERVICE_RECOVER_FAILED = "RecoverFailed" + SERVICE_UPGRADING = "Upgrading" + SERVICE_UPGRADE_FAILED = "UpgradeFailed" + SERVICE_DELETE_FAILED = "DeleteFailed" + // SERVICE_DELETED is a pseudo-status used internally by deletion polling: Get returns empty list + // indicating the instance is gone. + SERVICE_DELETED = "Deleted" +) diff --git a/products/urocketmq/internal/service/update_name.go b/products/urocketmq/internal/service/update_name.go new file mode 100644 index 0000000000..6e4204f243 --- /dev/null +++ b/products/urocketmq/internal/service/update_name.go @@ -0,0 +1,48 @@ +package service + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpdateName ucloud urocketmq service update-name +func newUpdateName(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewUpdateURocketMQServiceNameRequest() + cmd := &cobra.Command{ + Use: "update-name", + Short: "Update URocketMQ service instance name", + Long: "Update URocketMQ service instance name", + RunE: func(cmd *cobra.Command, args []string) error { + serviceID := *req.ServiceId + _, err := client.UpdateURocketMQServiceName(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: serviceID, Action: "update-name", Status: "Updated"}) + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ServiceId = flags.String("service-id", "", "Required. Service ID") + req.Name = flags.String("name", "", "Required. New service name. Regex: ^[a-zA-Z0-9-_]{1,36}$") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetCompletion(cmd, "service-id", func() []string { + return ServiceList(ctx, req.GetProjectId(), req.GetRegion()) + }) + + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("name") + + return cmd +} diff --git a/products/urocketmq/internal/service/update_remark.go b/products/urocketmq/internal/service/update_remark.go new file mode 100644 index 0000000000..390f82d4c6 --- /dev/null +++ b/products/urocketmq/internal/service/update_remark.go @@ -0,0 +1,47 @@ +package service + +import ( + "github.com/spf13/cobra" + + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpdateRemark ucloud urocketmq service update-remark +func newUpdateRemark(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewUpdateURocketMQServiceRemarkRequest() + cmd := &cobra.Command{ + Use: "update-remark", + Short: "Update URocketMQ service instance remark", + Long: "Update URocketMQ service instance remark", + RunE: func(cmd *cobra.Command, args []string) error { + serviceID := *req.ServiceId + _, err := client.UpdateURocketMQServiceRemark(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: serviceID, Action: "update-remark", Status: "Updated"}) + return nil + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.ServiceId = flags.String("service-id", "", "Required. Service ID") + req.Remark = flags.String("remark", "", "Optional. New remark for the service") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + + command.SetCompletion(cmd, "service-id", func() []string { + return ServiceList(ctx, req.GetProjectId(), req.GetRegion()) + }) + + cmd.MarkFlagRequired("service-id") + + return cmd +} diff --git a/products/urocketmq/internal/token/cmd.go b/products/urocketmq/internal/token/cmd.go new file mode 100644 index 0000000000..14ab6cd15a --- /dev/null +++ b/products/urocketmq/internal/token/cmd.go @@ -0,0 +1,24 @@ +package token + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `urocketmq token` resource-group command. Action subcommand order is fixed as +// create/delete/get/list/update (golden depends, do not reorder arbitrarily). +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "token", + Short: "Manage URocketMQ access tokens", + Long: "Create, delete, get, list and update URocketMQ access tokens for fine-grained topic access control.", + Args: cobra.NoArgs, + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newGet(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newUpdate(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/token/completion.go b/products/urocketmq/internal/token/completion.go new file mode 100644 index 0000000000..581e855d65 --- /dev/null +++ b/products/urocketmq/internal/token/completion.go @@ -0,0 +1,34 @@ +package token + +import ( + "github.com/ucloud/ucloud-cli/pkg/cli" + urocketmq "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" +) + +// TokenList returns the TokenId list under the specified Service, for same-group --token-id completion reuse. +// ListURocketMQToken Limit max is 100 (different from service/topic's 1000), +// paginates by 100 to fetch all, see group.GroupList. +func TokenList(ctx *cli.Context, projectID, region, serviceID string) []string { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQTokenRequest() + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + req.ServiceId = sdk.String(serviceID) + ids := make([]string, 0) + for limit, offset := 100, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.ListURocketMQToken(req) + if err != nil { + return nil + } + for _, t := range resp.TokenList { + ids = append(ids, t.TokenId) + } + if offset+limit >= resp.TotalCount { + break + } + } + return ids +} diff --git a/products/urocketmq/internal/token/create.go b/products/urocketmq/internal/token/create.go new file mode 100644 index 0000000000..af450db44f --- /dev/null +++ b/products/urocketmq/internal/token/create.go @@ -0,0 +1,70 @@ +package token + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// newCreate ucloud urocketmq token create +func newCreate(ctx *cli.Context) *cobra.Command { + var allowConsumeTopicList []string + var allowProduceTopicList []string + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewCreateURocketMQTokenRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create URocketMQ token", + Long: "Create URocketMQ token", + Run: func(cmd *cobra.Command, args []string) { + if len(allowConsumeTopicList) > 0 { + req.AllowConsumeTopicList = sdk.String(strings.Join(allowConsumeTopicList, ",")) + } + if len(allowProduceTopicList) > 0 { + req.AllowProduceTopicList = sdk.String(strings.Join(allowProduceTopicList, ",")) + } + resp, err := client.CreateURocketMQToken(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.TokenId, Action: "create", Status: "Success"}) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.Name = cmd.Flags().String("name", "", "Required. Token name") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. See 'ucloud urocketmq service list'") + req.TopicConsumePerm = cmd.Flags().String("topic-consume-perm", "", "Required. Topic consume permission. Accept values: ALL, NONE, PART") + req.TopicProducePerm = cmd.Flags().String("topic-produce-perm", "", "Required. Topic produce permission. Accept values: ALL, NONE, PART") + cmd.Flags().StringSliceVar(&allowConsumeTopicList, "allow-consume-topic-list", nil, "Optional. Allow consume topic name list, multiple values separated by comma") + cmd.Flags().StringSliceVar(&allowProduceTopicList, "allow-produce-topic-list", nil, "Optional. Allow produce topic name list, multiple values separated by comma") + + command.SetFlagValues(cmd, "topic-consume-perm", "ALL", "NONE", "PART") + command.SetFlagValues(cmd, "topic-produce-perm", "ALL", "NONE", "PART") + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "allow-consume-topic-list", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + command.SetCompletion(cmd, "allow-produce-topic-list", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("topic-consume-perm") + cmd.MarkFlagRequired("topic-produce-perm") + + return cmd +} diff --git a/products/urocketmq/internal/token/delete.go b/products/urocketmq/internal/token/delete.go new file mode 100644 index 0000000000..6c4a7fa580 --- /dev/null +++ b/products/urocketmq/internal/token/delete.go @@ -0,0 +1,60 @@ +package token + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newDelete ucloud urocketmq token delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewDeleteURocketMQTokenRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete URocketMQ token", + Long: "Delete URocketMQ token. Default token cannot be deleted.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete token %q from service %s?", *req.TokenId, *req.ServiceId)) + if err != nil { + return err + } + if !ok { + return nil + } + + _, err = client.DeleteURocketMQToken(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.TokenId, Action: "delete", Status: "Deleted"}) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. See 'ucloud urocketmq service list'") + req.TokenId = cmd.Flags().String("token-id", "", "Required. Token ID") + cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "token-id", func() []string { + return TokenList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("token-id") + + return cmd +} diff --git a/products/urocketmq/internal/token/get.go b/products/urocketmq/internal/token/get.go new file mode 100644 index 0000000000..9b156e10c1 --- /dev/null +++ b/products/urocketmq/internal/token/get.go @@ -0,0 +1,96 @@ +package token + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newGet ucloud urocketmq token get +func newGet(ctx *cli.Context) *cobra.Command { + var display bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewGetURocketMQTokenRequest() + cmd := &cobra.Command{ + Use: "get", + Short: "Get URocketMQ token details", + Long: "Get URocketMQ token details. AKSK secret key is only shown with --display for security.", + Run: func(cmd *cobra.Command, args []string) { + if display { + req.Display = sdk.String("true") + } else { + req.Display = sdk.String("false") + } + resp, err := client.GetURocketMQToken(req) + if err != nil { + ctx.HandleError(err) + return + } + renderTokenGet(ctx, &resp.Token, display) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. See 'ucloud urocketmq service list'") + req.TokenId = cmd.Flags().String("token-id", "", "Required. Token ID") + cmd.Flags().BoolVar(&display, "display", false, "Optional. Display AKSK secret key in plaintext") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "token-id", func() []string { + return TokenList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("token-id") + + return cmd +} + +func renderTokenGet(ctx *cli.Context, t *urocketmq.Token, showSecret bool) { + if showSecret { + ctx.PrintList([]tokenRowWithSecret{{ + TokenId: t.TokenId, + Name: t.Name, + TopicConsumePerm: t.TopicConsumePerm, + TopicProducePerm: t.TopicProducePerm, + Type: t.Type, + CreateTime: common.FormatDate(t.CreateTime), + ModifyTime: common.FormatDate(t.ModifyTime), + AccessKey: t.AKSK.AccessKey, + SecretKey: t.AKSK.SecretKey, + }}) + return + } + + if ctx.Format() != cli.OutputTable { + ctx.PrintList([]tokenRow{{ + TokenId: t.TokenId, + Name: t.Name, + TopicConsumePerm: t.TopicConsumePerm, + TopicProducePerm: t.TopicProducePerm, + Type: t.Type, + CreateTime: common.FormatDate(t.CreateTime), + ModifyTime: common.FormatDate(t.ModifyTime), + AccessKey: t.AKSK.AccessKey, + }}) + return + } + + ctx.PrintList([]tokenRowDefault{{ + TokenId: t.TokenId, + Name: t.Name, + TopicConsumePerm: t.TopicConsumePerm, + TopicProducePerm: t.TopicProducePerm, + Type: t.Type, + CreateTime: common.FormatDate(t.CreateTime), + }}) +} diff --git a/products/urocketmq/internal/token/list.go b/products/urocketmq/internal/token/list.go new file mode 100644 index 0000000000..a8d68dbb02 --- /dev/null +++ b/products/urocketmq/internal/token/list.go @@ -0,0 +1,80 @@ +package token + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newList ucloud urocketmq token list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQTokenRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List URocketMQ tokens", + Long: "List URocketMQ tokens", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.ListURocketMQToken(req) + if err != nil { + ctx.HandleError(err) + return + } + listToken(ctx, resp.TokenList) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID") + req.Limit = cmd.Flags().Int("limit", 20, "Optional. Limit default 20, max value 100") + req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset default 0") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("service-id") + + return cmd +} + +// listToken renders the token list. json/yaml emits full-field tokenRow; table mode uses tokenRowDefault. +func listToken(ctx *cli.Context, tokens []urocketmq.TokenDetail) { + list := make([]tokenRow, 0, len(tokens)) + for _, t := range tokens { + list = append(list, tokenRow{ + TokenId: t.TokenId, + Name: t.Name, + TopicConsumePerm: t.TopicConsumePerm, + TopicProducePerm: t.TopicProducePerm, + Type: t.Type, + CreateTime: common.FormatDate(t.CreateTime), + ModifyTime: common.FormatDate(t.ModifyTime), + AccessKey: t.AKSK.AccessKey, + }) + } + + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return + } + + rows := make([]tokenRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, tokenRowDefault{ + TokenId: r.TokenId, + Name: r.Name, + TopicConsumePerm: r.TopicConsumePerm, + TopicProducePerm: r.TopicProducePerm, + Type: r.Type, + CreateTime: r.CreateTime, + }) + } + ctx.PrintList(rows) +} diff --git a/products/urocketmq/internal/token/rows.go b/products/urocketmq/internal/token/rows.go new file mode 100644 index 0000000000..77d782ced4 --- /dev/null +++ b/products/urocketmq/internal/token/rows.go @@ -0,0 +1,36 @@ +package token + +// tokenRow is the full-field row (json/yaml mode), without SecretKey (secure default). +type tokenRow struct { + TokenId string + Name string + TopicConsumePerm string + TopicProducePerm string + Type string + CreateTime string + ModifyTime string + AccessKey string +} + +// tokenRowWithSecret is used for get --display, appending SecretKey on top of tokenRow. +type tokenRowWithSecret struct { + TokenId string + Name string + TopicConsumePerm string + TopicProducePerm string + Type string + CreateTime string + ModifyTime string + AccessKey string + SecretKey string +} + +// tokenRowDefault is the curated columns in table mode, containing no AKSK key information. +type tokenRowDefault struct { + TokenId string + Name string + TopicConsumePerm string + TopicProducePerm string + Type string + CreateTime string +} diff --git a/products/urocketmq/internal/token/update.go b/products/urocketmq/internal/token/update.go new file mode 100644 index 0000000000..dabcb5d01a --- /dev/null +++ b/products/urocketmq/internal/token/update.go @@ -0,0 +1,73 @@ +package token + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/topic" +) + +// newUpdate ucloud urocketmq token update +func newUpdate(ctx *cli.Context) *cobra.Command { + var allowConsumeTopicList []string + var allowProduceTopicList []string + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewUpdateURocketMQTokenRequest() + cmd := &cobra.Command{ + Use: "update", + Short: "Update URocketMQ token configuration", + Long: "Update URocketMQ token configuration", + Run: func(cmd *cobra.Command, args []string) { + if len(allowConsumeTopicList) > 0 { + req.AllowConsumeTopicList = sdk.String(strings.Join(allowConsumeTopicList, ",")) + } + if len(allowProduceTopicList) > 0 { + req.AllowProduceTopicList = sdk.String(strings.Join(allowProduceTopicList, ",")) + } + _, err := client.UpdateURocketMQToken(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.TokenId, Action: "update", Status: "Success"}) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. See 'ucloud urocketmq service list'") + req.TokenId = cmd.Flags().String("token-id", "", "Required. Token ID") + req.TopicConsumePerm = cmd.Flags().String("topic-consume-perm", "", "Required. Topic consume permission. Accept values: ALL, NONE, PART") + req.TopicProducePerm = cmd.Flags().String("topic-produce-perm", "", "Required. Topic produce permission. Accept values: ALL, NONE, PART") + cmd.Flags().StringSliceVar(&allowConsumeTopicList, "allow-consume-topic-list", nil, "Optional. Allow consume topic name list, multiple values separated by comma") + cmd.Flags().StringSliceVar(&allowProduceTopicList, "allow-produce-topic-list", nil, "Optional. Allow produce topic name list, multiple values separated by comma") + + command.SetFlagValues(cmd, "topic-consume-perm", "ALL", "NONE", "PART") + command.SetFlagValues(cmd, "topic-produce-perm", "ALL", "NONE", "PART") + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "token-id", func() []string { + return TokenList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + command.SetCompletion(cmd, "allow-consume-topic-list", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + command.SetCompletion(cmd, "allow-produce-topic-list", func() []string { + return topic.TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("token-id") + cmd.MarkFlagRequired("topic-consume-perm") + cmd.MarkFlagRequired("topic-produce-perm") + + return cmd +} diff --git a/products/urocketmq/internal/topic/cmd.go b/products/urocketmq/internal/topic/cmd.go new file mode 100644 index 0000000000..2adff2b613 --- /dev/null +++ b/products/urocketmq/internal/topic/cmd.go @@ -0,0 +1,22 @@ +package topic + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `urocketmq topic` resource-group command. Action subcommands are appended +// at the end in subsequent batches (order is fixed, golden depends). +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "topic", + Short: "Manage URocketMQ topics", + Args: cobra.NoArgs, + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newUpdate(ctx)) + return cmd +} diff --git a/products/urocketmq/internal/topic/completion.go b/products/urocketmq/internal/topic/completion.go new file mode 100644 index 0000000000..ec074fd91f --- /dev/null +++ b/products/urocketmq/internal/topic/completion.go @@ -0,0 +1,28 @@ +package topic + +import ( + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" +) + +// TopicList returns the topic name list under the specified service, for --topic-name completion reuse +// by delete/update (exported for use by other packages). +func TopicList(ctx *cli.Context, projectID, region, serviceID string) []string { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQTopicRequest() + req.ProjectId = sdk.String(projectID) + req.Region = sdk.String(region) + req.ServiceId = sdk.String(serviceID) + req.Limit = sdk.Int(1000) + req.Offset = sdk.Int(0) + resp, err := client.ListURocketMQTopic(req) + if err != nil { + return nil + } + names := make([]string, 0, len(resp.TopicList)) + for _, t := range resp.TopicList { + names = append(names, t.TopicName) + } + return names +} diff --git a/products/urocketmq/internal/topic/create.go b/products/urocketmq/internal/topic/create.go new file mode 100644 index 0000000000..9cc4a652e0 --- /dev/null +++ b/products/urocketmq/internal/topic/create.go @@ -0,0 +1,49 @@ +package topic + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newCreate ucloud urocketmq topic create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewCreateURocketMQTopicRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create URocketMQ topic", + Long: "Create URocketMQ topic", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + resp, err := client.CreateURocketMQTopic(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.TopicId, Action: "create", Status: "Success"}) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.MessageType = cmd.Flags().String("message-type", "", "Required. Message type. Accept values: Normal, PartitionSequence, GlobalSequence, Transaction, Delay") + req.Name = cmd.Flags().String("name", "", "Required. Topic name, supports letters, digits, hyphens and underscores, length 1~36") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. see 'ucloud urocketmq service list'") + req.Remark = cmd.Flags().String("remark", "", "Optional. Topic remark, max length 36") + + command.SetFlagValues(cmd, "message-type", "Normal", "PartitionSequence", "GlobalSequence", "Transaction", "Delay") + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("message-type") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("service-id") + + return cmd +} diff --git a/products/urocketmq/internal/topic/delete.go b/products/urocketmq/internal/topic/delete.go new file mode 100644 index 0000000000..1440b5d01e --- /dev/null +++ b/products/urocketmq/internal/topic/delete.go @@ -0,0 +1,59 @@ +package topic + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newDelete ucloud urocketmq topic delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes *bool + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewDeleteURocketMQTopicRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete URocketMQ topic", + Long: "Delete URocketMQ topic", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + ok, err := ctx.Confirm(*yes, fmt.Sprintf("Are you sure you want to delete topic %q from service %s?", *req.TopicName, *req.ServiceId)) + if err != nil { + return err + } + if !ok { + return nil + } + _, err = client.DeleteURocketMQTopic(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.TopicName, Action: "delete", Status: "Success"}) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. see 'ucloud urocketmq service list'") + req.TopicName = cmd.Flags().String("topic-name", "", "Required. Topic name") + yes = cmd.Flags().BoolP("yes", "y", false, "Optional. Do not prompt for confirmation.") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "topic-name", func() []string { + return TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("topic-name") + + return cmd +} diff --git a/products/urocketmq/internal/topic/list.go b/products/urocketmq/internal/topic/list.go new file mode 100644 index 0000000000..803338b515 --- /dev/null +++ b/products/urocketmq/internal/topic/list.go @@ -0,0 +1,80 @@ +package topic + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newList ucloud urocketmq topic list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewListURocketMQTopicRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List URocketMQ topics", + Long: "List URocketMQ topics", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.ListURocketMQTopic(req) + if err != nil { + ctx.HandleError(err) + return + } + listTopic(ctx, resp.TopicList) + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.Limit = cmd.Flags().Int("limit", 20, "Optional. Limit default 20, max value 1000") + req.Offset = cmd.Flags().Int("offset", 0, "Optional. Offset default 0") + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. see 'ucloud urocketmq service list'") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + cmd.MarkFlagRequired("service-id") + + return cmd +} + +// listTopic renders the topic list. json/yaml emits full-field topicRow; table mode uses curated columns topicRowDefault. +func listTopic(ctx *cli.Context, topics []urocketmq.TopicInfo) { + list := make([]topicRow, 0, len(topics)) + for _, t := range topics { + list = append(list, toTopicRow(t)) + } + + if ctx.Format() != cli.OutputTable { + ctx.PrintList(list) + return + } + + rows := make([]topicRowDefault, 0, len(list)) + for _, r := range list { + rows = append(rows, topicRowDefault{ + TopicName: r.TopicName, + MessageType: r.MessageType, + Remark: r.Remark, + CreateTime: common.FormatDate(r.CreateTime), + }) + } + ctx.PrintList(rows) +} + +// toTopicRow maps SDK TopicInfo to a full-field row. +func toTopicRow(t urocketmq.TopicInfo) topicRow { + return topicRow{ + TopicId: t.TopicId, + TopicName: t.TopicName, + MessageType: t.MessageType, + Remark: t.Remark, + CreateTime: t.CreateTime, + } +} diff --git a/products/urocketmq/internal/topic/rows.go b/products/urocketmq/internal/topic/rows.go new file mode 100644 index 0000000000..f91a680a62 --- /dev/null +++ b/products/urocketmq/internal/topic/rows.go @@ -0,0 +1,18 @@ +package topic + +// topicRow is the full-field row (json/yaml mode). Fields correspond to SDK TopicInfo. +type topicRow struct { + TopicId string + TopicName string + MessageType string + Remark string + CreateTime int +} + +// topicRowDefault is the default curated columns in table mode: TopicName/MessageType/Remark/CreateTime. +type topicRowDefault struct { + TopicName string + MessageType string + Remark string + CreateTime string +} diff --git a/products/urocketmq/internal/topic/update.go b/products/urocketmq/internal/topic/update.go new file mode 100644 index 0000000000..412a5a4f22 --- /dev/null +++ b/products/urocketmq/internal/topic/update.go @@ -0,0 +1,49 @@ +package topic + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/urocketmq" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" + "github.com/ucloud/ucloud-cli/products/urocketmq/internal/service" +) + +// newUpdate ucloud urocketmq topic update +func newUpdate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, urocketmq.NewClient) + req := client.NewUpdateURocketMQTopicRequest() + cmd := &cobra.Command{ + Use: "update", + Short: "Update URocketMQ topic remark", + Long: "Update URocketMQ topic remark. Currently only supports updating the topic remark.", + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) error { + _, err := client.UpdateURocketMQTopic(req) + if err != nil { + return err + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.TopicName, Action: "update", Status: "Success"}) + return nil + }, + } + cmd.Flags().SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + req.ServiceId = cmd.Flags().String("service-id", "", "Required. Service ID. see 'ucloud urocketmq service list'") + req.TopicName = cmd.Flags().String("topic-name", "", "Required. Topic name") + req.Remark = cmd.Flags().String("remark", "", "Optional. Topic remark") + + command.SetCompletion(cmd, "service-id", func() []string { + return service.ServiceList(ctx, *req.ProjectId, *req.Region) + }) + command.SetCompletion(cmd, "topic-name", func() []string { + return TopicList(ctx, *req.ProjectId, *req.Region, *req.ServiceId) + }) + cmd.MarkFlagRequired("service-id") + cmd.MarkFlagRequired("topic-name") + + return cmd +} diff --git a/products/urocketmq/product.go b/products/urocketmq/product.go new file mode 100644 index 0000000000..cf896e1573 --- /dev/null +++ b/products/urocketmq/product.go @@ -0,0 +1,21 @@ +package urocketmq + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internal "github.com/ucloud/ucloud-cli/products/urocketmq/internal" +) + +type product struct{} + +// New returns the urocketmq product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "urocketmq", Commands: []string{"urocketmq"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internal.NewCommand(ctx)} +} diff --git a/products/urocketmq/product.yaml b/products/urocketmq/product.yaml new file mode 100644 index 0000000000..76ed48b734 --- /dev/null +++ b/products/urocketmq/product.yaml @@ -0,0 +1,6 @@ +name: urocketmq +owners: + - lanfunoe +commands: + - urocketmq +enabled: true diff --git a/products/urocketmq/testdata/cmdtree.golden b/products/urocketmq/testdata/cmdtree.golden new file mode 100644 index 0000000000..88a8f813ec --- /dev/null +++ b/products/urocketmq/testdata/cmdtree.golden @@ -0,0 +1,159 @@ +ucloud urocketmq use=urocketmq short=Manage URocketMQ instances, topics, groups, tokens and messages +ucloud urocketmq group use=group short=Manage URocketMQ consumer groups +ucloud urocketmq group create use=create short=Create a consumer group + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq group delete use=delete short=Delete a consumer group + flag=group-name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=yes short=y default=false required= +ucloud urocketmq group list use=list short=List URocketMQ consumer groups + 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=true +ucloud urocketmq message use=message short=Query URocketMQ messages +ucloud urocketmq message query-by-id use=query-by-id short=Query a message by ID + flag=msg-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=topic-name short= default= required=true +ucloud urocketmq message query-by-key use=query-by-key short=Query messages by key + flag=id-only short= default=false required= + flag=key short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=topic-name short= default= required=true +ucloud urocketmq message query-by-topic use=query-by-topic short=Query messages by topic and time range + flag=begin short= default= required=true + flag=end short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=topic-name short= default= required=true +ucloud urocketmq service use=service short=Manage URocketMQ service instances +ucloud urocketmq service create use=create short=Create a URocketMQ service instance + flag=async short= default=false required= + flag=charge-type short= default=Month required=true + flag=edition short= default=Enterprise required=true + flag=file-reserved-time short= default=3 required= + flag=group short= default=Default required= + flag=mode short= default=PrivateNet required=true + flag=name short= default= required=true + flag=project-id short= default= required= + flag=public-version short= default= required=true + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=remark short= default= required= + flag=storage-gb short= default=0 required=true + flag=subnet-id short= default= required=true + flag=tps short= default= required=true + flag=vpc-id short= default= required=true +ucloud urocketmq service delete use=delete short=Delete a URocketMQ service instance + flag=async short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=yes short=y default=false required= +ucloud urocketmq service get use=get short=Get details of a URocketMQ service instance + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq service list use=list short=List all URocketMQ instances + flag=all-region short= default=false required= + flag=id-only short= default=false required= + flag=limit short= default=20 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= +ucloud urocketmq service price use=price short=Get price of URocketMQ service instance + flag=charge-type short= default=Month required=true + flag=edition short= default=Enterprise required=true + flag=mode short= default=PrivateNet required=true + flag=project-id short= default= required= + flag=public-version short= default= required=true + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=storage-gb short= default=0 required=true + flag=tps short= default=0 required=true +ucloud urocketmq service update-name use=update-name short=Update URocketMQ service instance name + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq service update-remark use=update-remark short=Update URocketMQ service instance remark + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq token use=token short=Manage URocketMQ access tokens +ucloud urocketmq token create use=create short=Create URocketMQ token + flag=allow-consume-topic-list short= default=[] required= + flag=allow-produce-topic-list short= default=[] required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=topic-consume-perm short= default= required=true + flag=topic-produce-perm short= default= required=true +ucloud urocketmq token delete use=delete short=Delete URocketMQ token + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=token-id short= default= required=true + flag=yes short=y default=false required= +ucloud urocketmq token get use=get short=Get URocketMQ token details + flag=display short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=token-id short= default= required=true +ucloud urocketmq token list use=list short=List URocketMQ tokens + flag=limit short= default=20 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq token update use=update short=Update URocketMQ token configuration + flag=allow-consume-topic-list short= default=[] required= + flag=allow-produce-topic-list short= default=[] required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=token-id short= default= required=true + flag=topic-consume-perm short= default= required=true + flag=topic-produce-perm short= default= required=true +ucloud urocketmq topic use=topic short=Manage URocketMQ topics +ucloud urocketmq topic create use=create short=Create URocketMQ topic + flag=message-type short= default= required=true + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq topic delete use=delete short=Delete URocketMQ topic + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true + flag=topic-name short= default= required=true + flag=yes short=y default=false required= +ucloud urocketmq topic list use=list short=List URocketMQ topics + flag=limit short= default=20 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=service-id short= default= required=true +ucloud urocketmq topic update use=update short=Update URocketMQ topic remark + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required= + flag=service-id short= default= required=true + flag=topic-name short= default= required=true diff --git a/products/urocketmq/testdata/completion.golden b/products/urocketmq/testdata/completion.golden new file mode 100644 index 0000000000..b720fa0fb3 --- /dev/null +++ b/products/urocketmq/testdata/completion.golden @@ -0,0 +1,96 @@ +ucloud urocketmq group create project-id dynamic +ucloud urocketmq group create region dynamic +ucloud urocketmq group create service-id dynamic +ucloud urocketmq group delete group-name dynamic +ucloud urocketmq group delete project-id dynamic +ucloud urocketmq group delete region dynamic +ucloud urocketmq group delete service-id dynamic +ucloud urocketmq group list project-id dynamic +ucloud urocketmq group list region dynamic +ucloud urocketmq group list service-id dynamic +ucloud urocketmq message query-by-id project-id dynamic +ucloud urocketmq message query-by-id region dynamic +ucloud urocketmq message query-by-id service-id dynamic +ucloud urocketmq message query-by-id topic-name dynamic +ucloud urocketmq message query-by-key id-only static false,true +ucloud urocketmq message query-by-key project-id dynamic +ucloud urocketmq message query-by-key region dynamic +ucloud urocketmq message query-by-key service-id dynamic +ucloud urocketmq message query-by-key topic-name dynamic +ucloud urocketmq message query-by-topic project-id dynamic +ucloud urocketmq message query-by-topic region dynamic +ucloud urocketmq message query-by-topic service-id dynamic +ucloud urocketmq message query-by-topic topic-name dynamic +ucloud urocketmq service create charge-type static Dynamic,Month,Year +ucloud urocketmq service create edition static Enterprise +ucloud urocketmq service create mode static PrivateNet +ucloud urocketmq service create project-id dynamic +ucloud urocketmq service create public-version static v4,v5 +ucloud urocketmq service create region dynamic +ucloud urocketmq service create subnet-id dynamic +ucloud urocketmq service create tps static 10000,100000,20000,200000,50000 +ucloud urocketmq service create vpc-id dynamic +ucloud urocketmq service delete project-id dynamic +ucloud urocketmq service delete region dynamic +ucloud urocketmq service delete service-id dynamic +ucloud urocketmq service get project-id dynamic +ucloud urocketmq service get region dynamic +ucloud urocketmq service get service-id dynamic +ucloud urocketmq service list all-region static false,true +ucloud urocketmq service list id-only static false,true +ucloud urocketmq service list project-id dynamic +ucloud urocketmq service list region dynamic +ucloud urocketmq service price charge-type static Dynamic,Month,Year +ucloud urocketmq service price edition static Enterprise +ucloud urocketmq service price mode static PrivateNet +ucloud urocketmq service price project-id dynamic +ucloud urocketmq service price public-version static v4,v5 +ucloud urocketmq service price region dynamic +ucloud urocketmq service price tps static 10000,100000,20000,200000,50000 +ucloud urocketmq service update-name project-id dynamic +ucloud urocketmq service update-name region dynamic +ucloud urocketmq service update-name service-id dynamic +ucloud urocketmq service update-remark project-id dynamic +ucloud urocketmq service update-remark region dynamic +ucloud urocketmq service update-remark service-id dynamic +ucloud urocketmq token create allow-consume-topic-list dynamic +ucloud urocketmq token create allow-produce-topic-list dynamic +ucloud urocketmq token create project-id dynamic +ucloud urocketmq token create region dynamic +ucloud urocketmq token create service-id dynamic +ucloud urocketmq token create topic-consume-perm static ALL,NONE,PART +ucloud urocketmq token create topic-produce-perm static ALL,NONE,PART +ucloud urocketmq token delete project-id dynamic +ucloud urocketmq token delete region dynamic +ucloud urocketmq token delete service-id dynamic +ucloud urocketmq token delete token-id dynamic +ucloud urocketmq token get project-id dynamic +ucloud urocketmq token get region dynamic +ucloud urocketmq token get service-id dynamic +ucloud urocketmq token get token-id dynamic +ucloud urocketmq token list project-id dynamic +ucloud urocketmq token list region dynamic +ucloud urocketmq token list service-id dynamic +ucloud urocketmq token update allow-consume-topic-list dynamic +ucloud urocketmq token update allow-produce-topic-list dynamic +ucloud urocketmq token update project-id dynamic +ucloud urocketmq token update region dynamic +ucloud urocketmq token update service-id dynamic +ucloud urocketmq token update token-id dynamic +ucloud urocketmq token update topic-consume-perm static ALL,NONE,PART +ucloud urocketmq token update topic-produce-perm static ALL,NONE,PART +ucloud urocketmq topic create message-type static Delay,GlobalSequence,Normal,PartitionSequence,Transaction +ucloud urocketmq topic create project-id dynamic +ucloud urocketmq topic create region dynamic +ucloud urocketmq topic create service-id dynamic +ucloud urocketmq topic delete project-id dynamic +ucloud urocketmq topic delete region dynamic +ucloud urocketmq topic delete service-id dynamic +ucloud urocketmq topic delete topic-name dynamic +ucloud urocketmq topic list project-id dynamic +ucloud urocketmq topic list region dynamic +ucloud urocketmq topic list service-id dynamic +ucloud urocketmq topic update project-id dynamic +ucloud urocketmq topic update region dynamic +ucloud urocketmq topic update service-id dynamic +ucloud urocketmq topic update topic-name dynamic