From 223573650f36de56426a0575c84d10cd39823bcc Mon Sep 17 00:00:00 2001 From: guleilei Date: Fri, 10 Jul 2026 15:04:28 +0800 Subject: [PATCH 1/3] Add css product --- cmd/products.gen.go | 2 + go.mod | 2 +- go.sum | 4 +- products/css/internal/css/appversion.go | 44 +++++++++++ products/css/internal/css/cmd.go | 27 +++++++ products/css/internal/css/completion.go | 44 +++++++++++ products/css/internal/css/create.go | 84 +++++++++++++++++++++ products/css/internal/css/delete.go | 64 ++++++++++++++++ products/css/internal/css/describe.go | 99 +++++++++++++++++++++++++ products/css/internal/css/disklimit.go | 44 +++++++++++ products/css/internal/css/expand.go | 62 ++++++++++++++++ products/css/internal/css/list.go | 57 ++++++++++++++ products/css/internal/css/nodeconf.go | 51 +++++++++++++ products/css/internal/css/resize.go | 63 ++++++++++++++++ products/css/internal/css/restart.go | 67 +++++++++++++++++ products/css/internal/css/rows.go | 41 ++++++++++ products/css/internal/css/status.go | 11 +++ products/css/product.go | 21 ++++++ products/css/product.yaml | 7 ++ products/css/testdata/cmdtree.golden | 78 +++++++++++++++++++ products/css/testdata/completion.golden | 8 ++ 21 files changed, 877 insertions(+), 3 deletions(-) create mode 100644 products/css/internal/css/appversion.go create mode 100644 products/css/internal/css/cmd.go create mode 100644 products/css/internal/css/completion.go create mode 100644 products/css/internal/css/create.go create mode 100644 products/css/internal/css/delete.go create mode 100644 products/css/internal/css/describe.go create mode 100644 products/css/internal/css/disklimit.go create mode 100644 products/css/internal/css/expand.go create mode 100644 products/css/internal/css/list.go create mode 100644 products/css/internal/css/nodeconf.go create mode 100644 products/css/internal/css/resize.go create mode 100644 products/css/internal/css/restart.go create mode 100644 products/css/internal/css/rows.go create mode 100644 products/css/internal/css/status.go create mode 100644 products/css/product.go create mode 100644 products/css/product.yaml create mode 100644 products/css/testdata/cmdtree.golden create mode 100644 products/css/testdata/completion.golden diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 180d47f326..b2ac8c85ec 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/products/css" "github.com/ucloud/ucloud-cli/products/eip" "github.com/ucloud/ucloud-cli/products/firewall" "github.com/ucloud/ucloud-cli/products/globalssh" @@ -28,6 +29,7 @@ import ( // registeredProducts returns the platform-registered products. func registeredProducts() []cli.Product { return []cli.Product{ + css.New(), eip.New(), firewall.New(), globalssh.New(), diff --git a/go.mod b/go.mod index fdf0db06af..cbdd673b5f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/ucloud/ucloud-sdk-go v0.22.87 + github.com/ucloud/ucloud-sdk-go v0.22.89 golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 0a0eb8db2d..15710e52f7 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ucloud/ucloud-sdk-go v0.22.87 h1:IAQBH1bl27+nBlBFo/hx5mMuDOMQHFks7cpOTfgxK3U= -github.com/ucloud/ucloud-sdk-go v0.22.87/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= +github.com/ucloud/ucloud-sdk-go v0.22.89 h1:eCvCtkan5KIzzBv+QJgEQojCbwwS+hVtOq52hfGnWtg= +github.com/ucloud/ucloud-sdk-go v0.22.89/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/products/css/internal/css/appversion.go b/products/css/internal/css/appversion.go new file mode 100644 index 0000000000..30cf9469a1 --- /dev/null +++ b/products/css/internal/css/appversion.go @@ -0,0 +1,44 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newAppVersion ucloud css app-version +func newAppVersion(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewGetUESAppVersionRequest() + cmd := &cobra.Command{ + Use: "app-version", + Short: "List available UES application versions", + Long: "List available UES application versions", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.GetUESAppVersion(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []AppVersionRow{} + for _, v := range resp.AppVersionList { + list = append(list, AppVersionRow{ + AppName: v.AppName, + AppVersion: v.AppVersion, + IsMultiZone: fmt.Sprintf("%t", v.IsMultiZone), + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + return cmd +} diff --git a/products/css/internal/css/cmd.go b/products/css/internal/css/cmd.go new file mode 100644 index 0000000000..43c9113942 --- /dev/null +++ b/products/css/internal/css/cmd.go @@ -0,0 +1,27 @@ +package css + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `css` root command and mounts the subcommands. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "css", + Short: "Manage UES (Elasticsearch/OpenSearch) instances", + Long: "Manage UES (Elasticsearch/OpenSearch) instances", + } + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newDescribe(ctx)) + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newExpand(ctx)) + cmd.AddCommand(newResize(ctx)) + cmd.AddCommand(newRestart(ctx)) + cmd.AddCommand(newDiskLimit(ctx)) + cmd.AddCommand(newNodeConf(ctx)) + cmd.AddCommand(newAppVersion(ctx)) + return cmd +} diff --git a/products/css/internal/css/completion.go b/products/css/internal/css/completion.go new file mode 100644 index 0000000000..ac2b1db755 --- /dev/null +++ b/products/css/internal/css/completion.go @@ -0,0 +1,44 @@ +package css + +import ( + "strings" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// getInstanceList returns "InstanceId/Name" completion candidates for instance-id flags. +func getInstanceList(ctx *cli.Context, states []string, project, region, zone string) []string { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewListUESInstanceRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + if zone != "" { + req.Zone = sdk.String(zone) + } + req.Limit = sdk.Int(50) + resp, err := client.ListUESInstance(req) + if err != nil { + // silent fail for completion + return nil + } + list := []string{} + for _, ins := range resp.ClusterSet { + if states != nil { + matched := false + for _, s := range states { + if ins.State == s { + matched = true + break + } + } + if !matched { + continue + } + } + list = append(list, ins.InstanceId+"/"+strings.Replace(ins.InstanceName, " ", "-", -1)) + } + return list +} diff --git a/products/css/internal/css/create.go b/products/css/internal/css/create.go new file mode 100644 index 0000000000..72ad537961 --- /dev/null +++ b/products/css/internal/css/create.go @@ -0,0 +1,84 @@ +package css + +import ( + "encoding/base64" + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud css create +func newCreate(ctx *cli.Context) *cobra.Command { + var async *bool + var servicePasswd *string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewCreateUESInstanceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create UES instance", + Long: "Create UES instance", + Run: func(cmd *cobra.Command, args []string) { + w := ctx.ProgressWriter() + // 服务密码需 base64 编码后提交;未指定时使用默认密码 changeme + passwd := *servicePasswd + if passwd == "" { + passwd = "changeme" + } + req.ServicePasswd = sdk.String(base64.StdEncoding.EncodeToString([]byte(passwd))) + resp, err := client.CreateUESInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + text := fmt.Sprintf("ues[%s] is creating", resp.InstanceId) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeUESInstanceByID(ctx)).Spoll(resp.InstanceId, text, []string{STATE_RUNNING, STATE_ABNORMAL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.InstanceId, Action: "create", Status: "Creating"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.InstanceName = flags.String("name", "", "Required. Instance name") + req.AppVersion = flags.String("app-version", "", "Required. Application version, e.g. 7.10.0") + req.AppName = flags.String("app-name", "elasticsearch", "Optional. Application name, default elasticsearch") + req.NodeConf = flags.String("node-conf", "", "Required. Node configuration identifier") + req.NodeDiskConf = flags.String("node-disk-conf", "CLOUD_RSSD", "Required. Node disk type") + req.NodeDiskSize = flags.Int("node-disk-size-gb", 100, "Optional. Node disk size in GB, default 100") + req.NodeSize = flags.Int("node-count", 3, "Optional. Node count, default 3") + req.KibanaNodeConf = flags.String("kibana-node-conf", "", "Required. Kibana node configuration") + req.KibanaNodeDiskConf = flags.String("kibana-disk-conf", "CLOUD_RSSD", "Required. Kibana disk type") + req.VPCId = flags.String("vpc-id", "", "Required. VPC ID") + req.SubnetId = flags.String("subnet-id", "", "Required. Subnet ID") + req.ServiceUserName = flags.String("service-username", "", "Optional. Service username. elasticsearch default 'elastic'; OpenSearch fixed 'admin'") + servicePasswd = flags.String("service-passwd", "", "Optional. Service password, default 'changeme'") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + req.ChargeType = flags.String("charge-type", "Month", "Optional. 'Year', 'Month', or 'Dynamic', default Month") + req.Quantity = flags.Int("quantity", 1, "Optional. Purchase duration, default 1") + req.BusinessId = flags.String("business-id", "", "Optional. Business group ID") + req.Remark = flags.String("remark", "", "Optional. Remark") + async = flags.Bool("async", false, "Optional. Do not wait for creation to finish") + + command.SetFlagValues(cmd, "charge-type", "Month", "Year", "Dynamic") + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("app-version") + cmd.MarkFlagRequired("node-conf") + cmd.MarkFlagRequired("node-disk-conf") + cmd.MarkFlagRequired("kibana-node-conf") + cmd.MarkFlagRequired("kibana-disk-conf") + cmd.MarkFlagRequired("vpc-id") + cmd.MarkFlagRequired("subnet-id") + + return cmd +} diff --git a/products/css/internal/css/delete.go b/products/css/internal/css/delete.go new file mode 100644 index 0000000000..802eb65872 --- /dev/null +++ b/products/css/internal/css/delete.go @@ -0,0 +1,64 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete ucloud css delete +func newDelete(ctx *cli.Context) *cobra.Command { + var yes *bool + var instanceIDs *[]string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewDeleteUESInstanceRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete UES instances", + Long: "Delete UES instances", + Run: func(cmd *cobra.Command, args []string) { + ok, err := ctx.Confirm(*yes, "Are you sure to delete UES instance(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idName := range *instanceIDs { + id := ctx.PickResourceID(idName) + req.InstanceId = &id + _, err := client.DeleteUESInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(w, "ues[%s] deleted\n", id) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + instanceIDs = flags.StringSlice("instance-id", nil, "Required. Instance ID(s) to delete") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + yes = flags.BoolP("yes", "y", false, "Optional. Skip confirmation prompt") + + command.SetCompletion(cmd, "instance-id", func() []string { + return getInstanceList(ctx, []string{STATE_RUNNING, STATE_STOPPED, STATE_ABNORMAL}, *req.ProjectId, *req.Region, "") + }) + + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/css/internal/css/describe.go b/products/css/internal/css/describe.go new file mode 100644 index 0000000000..82740e9e3f --- /dev/null +++ b/products/css/internal/css/describe.go @@ -0,0 +1,99 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + 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" +) + +// newDescribe ucloud css describe +func newDescribe(ctx *cli.Context) *cobra.Command { + var instanceID *string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewDescribeUESInstanceV2Request() + cmd := &cobra.Command{ + Use: "describe", + Short: "Describe UES instance details", + Long: "Describe UES instance details", + Run: func(cmd *cobra.Command, args []string) { + id := ctx.PickResourceID(*instanceID) + req.InstanceId = sdk.String(id) + resp, err := client.DescribeUESInstanceV2(req) + if err != nil { + ctx.HandleError(err) + return + } + cluster := resp.Result.ClusterInfo + rows := []cli.DescribeRow{ + {Attribute: "InstanceID", Content: cluster.UESInstanceId}, + {Attribute: "InstanceName", Content: cluster.UESInstanceName}, + {Attribute: "Region", Content: cluster.Region}, + {Attribute: "Zone", Content: cluster.Zone}, + {Attribute: "State", Content: cluster.State}, + {Attribute: "ServiceVersion", Content: cluster.ServiceVersion}, + {Attribute: "VPCId", Content: cluster.VPCId}, + {Attribute: "SubnetId", Content: cluster.SubnetId}, + {Attribute: "VIP", Content: cluster.Vip}, + {Attribute: "BusinessId", Content: cluster.BusinessId}, + } + // Add node information + if len(resp.Result.NodeInfoList) > 0 { + rows = append(rows, cli.DescribeRow{Attribute: "--- Nodes ---", Content: fmt.Sprintf("%d nodes", len(resp.Result.NodeInfoList))}) + for i, node := range resp.Result.NodeInfoList { + prefix := fmt.Sprintf("Node[%d]", i) + rows = append(rows, + cli.DescribeRow{Attribute: prefix + ".NodeID", Content: node.NodeId}, + cli.DescribeRow{Attribute: prefix + ".NodeName", Content: node.NodeName}, + cli.DescribeRow{Attribute: prefix + ".NodeRole", Content: node.NodeRole}, + cli.DescribeRow{Attribute: prefix + ".NodeState", Content: node.NodeState}, + cli.DescribeRow{Attribute: prefix + ".NodeIP", Content: node.NodeIP}, + cli.DescribeRow{Attribute: prefix + ".NodeConf", Content: node.NodeConf}, + cli.DescribeRow{Attribute: prefix + ".CPU", Content: fmt.Sprintf("%d", node.CPU)}, + cli.DescribeRow{Attribute: prefix + ".Memory", Content: fmt.Sprintf("%dGB", node.Memory)}, + cli.DescribeRow{Attribute: prefix + ".DiskSize", Content: fmt.Sprintf("%dGB", node.DiskSize)}, + cli.DescribeRow{Attribute: prefix + ".DiskType", Content: node.DiskType}, + ) + } + } + ctx.PrintList(rows) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + instanceID = flags.String("instance-id", "", "Required. Instance ID to describe") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + + command.SetCompletion(cmd, "instance-id", func() []string { + return getInstanceList(ctx, nil, *req.ProjectId, *req.Region, "") + }) + + cmd.MarkFlagRequired("instance-id") + + return cmd +} + +// describeUESInstanceByID returns the poller's describe func +func describeUESInstanceByID(ctx *cli.Context) func(instanceID string, commonBase *request.CommonBase) (interface{}, error) { + return func(instanceID string, commonBase *request.CommonBase) (interface{}, error) { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewDescribeUESInstanceV2Request() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.InstanceId = sdk.String(instanceID) + resp, err := client.DescribeUESInstanceV2(req) + if err != nil { + return nil, err + } + return &resp.Result.ClusterInfo, nil + } +} diff --git a/products/css/internal/css/disklimit.go b/products/css/internal/css/disklimit.go new file mode 100644 index 0000000000..e56a2f18e5 --- /dev/null +++ b/products/css/internal/css/disklimit.go @@ -0,0 +1,44 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newDiskLimit ucloud css disk-limit +func newDiskLimit(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewGetUESDiskSizeLimitationRequest() + cmd := &cobra.Command{ + Use: "disk-limit", + Short: "List UES disk size limitations by disk type", + Long: "List UES disk size limitations by disk type", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.GetUESDiskSizeLimitation(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []DiskLimitRow{} + for _, d := range resp.DiskSizeLimitationSet { + list = append(list, DiskLimitRow{ + DiskType: d.DiskType, + MinSizeGB: fmt.Sprintf("%d", d.MinSize), + MaxSizeGB: fmt.Sprintf("%d", d.MaxSize), + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + return cmd +} diff --git a/products/css/internal/css/expand.go b/products/css/internal/css/expand.go new file mode 100644 index 0000000000..f825db4085 --- /dev/null +++ b/products/css/internal/css/expand.go @@ -0,0 +1,62 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newExpand ucloud css expand +func newExpand(ctx *cli.Context) *cobra.Command { + var async *bool + var instanceID *string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewExpandUESInstanceRequest() + cmd := &cobra.Command{ + Use: "expand", + Short: "Expand UES instance node count", + Long: "Expand UES instance node count", + Run: func(cmd *cobra.Command, args []string) { + id := ctx.PickResourceID(*instanceID) + req.InstanceId = &id + w := ctx.ProgressWriter() + _, err := client.ExpandUESInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + text := fmt.Sprintf("ues[%s] is expanding", id) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeUESInstanceByID(ctx)).Spoll(id, text, []string{STATE_RUNNING, STATE_ABNORMAL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: id, Action: "expand", Status: "Expanding"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + instanceID = flags.String("instance-id", "", "Required. Instance ID to expand") + req.NodeRole = flags.String("node-role", "", "Required. Node role to expand ('compute', 'coordinating')") + req.NodeCount = flags.Int("node-count", 0, "Required. Node count after expansion") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + async = flags.Bool("async", false, "Optional. Do not wait for expansion to finish") + + command.SetFlagValues(cmd, "node-role", "compute", "coordinating") + command.SetCompletion(cmd, "instance-id", func() []string { + return getInstanceList(ctx, []string{STATE_RUNNING}, *req.ProjectId, *req.Region, "") + }) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("node-role") + cmd.MarkFlagRequired("node-count") + + return cmd +} diff --git a/products/css/internal/css/list.go b/products/css/internal/css/list.go new file mode 100644 index 0000000000..075d459e11 --- /dev/null +++ b/products/css/internal/css/list.go @@ -0,0 +1,57 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList ucloud css list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewListUESInstanceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List UES instances", + Long: "List UES instances", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.ListUESInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []InstanceRow{} + for _, ins := range resp.ClusterSet { + row := InstanceRow{ + InstanceID: ins.InstanceId, + InstanceName: ins.InstanceName, + AppName: ins.AppName, + AppVersion: ins.AppVersion, + Zone: ins.Zone, + State: ins.State, + NodeCount: fmt.Sprintf("%d", ins.NodeCount), + VPCId: ins.VPCId, + SubnetId: ins.SubnetId, + ChargeType: ins.ChargeType, + CreateTime: common.FormatDate(ins.CreateTime), + ExpireTime: common.FormatDate(ins.ExpireTime), + } + list = append(list, row) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", "", "Optional. Assign availability zone") + req.Offset = flags.Int("offset", 0, "Optional. Offset") + req.Limit = flags.Int("limit", 30, "Optional. Limit, default 30") + return cmd +} diff --git a/products/css/internal/css/nodeconf.go b/products/css/internal/css/nodeconf.go new file mode 100644 index 0000000000..e1b5f1f8f7 --- /dev/null +++ b/products/css/internal/css/nodeconf.go @@ -0,0 +1,51 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newNodeConf ucloud css node-conf +func newNodeConf(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewGetUESNodeConfRequest() + cmd := &cobra.Command{ + Use: "node-conf", + Short: "List available UES node configurations", + Long: "List available UES node configurations", + Run: func(cmd *cobra.Command, args []string) { + resp, err := client.GetUESNodeConf(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []NodeConfRow{} + for _, n := range resp.NodeConfList { + list = append(list, NodeConfRow{ + NodeConf: n.NodeConf, + CPU: fmt.Sprintf("%d", n.CPU), + MemoryGB: fmt.Sprintf("%d", n.Memory), + DiskSizeGB: fmt.Sprintf("%d", n.DiskSize), + DiskType: n.DiskType, + SecGroup: fmt.Sprintf("%t", n.IsSecGroup), + }) + } + ctx.PrintList(list) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + req.AppVersion = flags.String("app-version", "", "Required. Application version, e.g. elasticsearch-7.10.0") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + + cmd.MarkFlagRequired("app-version") + + return cmd +} diff --git a/products/css/internal/css/resize.go b/products/css/internal/css/resize.go new file mode 100644 index 0000000000..f726f132d6 --- /dev/null +++ b/products/css/internal/css/resize.go @@ -0,0 +1,63 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newResize ucloud css resize +func newResize(ctx *cli.Context) *cobra.Command { + var async *bool + var instanceID *string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewResizeUESInstanceRequest() + cmd := &cobra.Command{ + Use: "resize", + Short: "Resize UES instance node configuration", + Long: "Resize UES instance node configuration. Set node-conf to change spec, or node-disk-size-gb to change disk (leave the other at zero/empty).", + Run: func(cmd *cobra.Command, args []string) { + id := ctx.PickResourceID(*instanceID) + req.InstanceId = &id + w := ctx.ProgressWriter() + _, err := client.ResizeUESInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + text := fmt.Sprintf("ues[%s] is resizing", id) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeUESInstanceByID(ctx)).Spoll(id, text, []string{STATE_RUNNING, STATE_ABNORMAL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: id, Action: "resize", Status: "Resizing"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + instanceID = flags.String("instance-id", "", "Required. Instance ID to resize") + req.NodeRole = flags.String("node-role", "", "Required. Node role ('compute', 'master', 'coordinating', 'kibana', 'dashboard')") + req.NodeConf = flags.String("node-conf", "", "Optional. Target node configuration. When empty, resize by node-disk-size-gb") + req.NodeDiskSize = flags.Int("node-disk-size-gb", 0, "Optional. Target node disk size in GB. When 0, resize by node-conf") + req.ForceResizing = flags.Bool("force", false, "Optional. Force resize without cluster health check, default false") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + async = flags.Bool("async", false, "Optional. Do not wait for resize to finish") + + command.SetFlagValues(cmd, "node-role", "compute", "master", "coordinating", "kibana", "dashboard") + command.SetCompletion(cmd, "instance-id", func() []string { + return getInstanceList(ctx, []string{STATE_RUNNING}, *req.ProjectId, *req.Region, "") + }) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("node-role") + + return cmd +} diff --git a/products/css/internal/css/restart.go b/products/css/internal/css/restart.go new file mode 100644 index 0000000000..4339eedd2d --- /dev/null +++ b/products/css/internal/css/restart.go @@ -0,0 +1,67 @@ +package css + +import ( + "fmt" + + "github.com/spf13/cobra" + + uessdk "github.com/ucloud/ucloud-sdk-go/services/ues" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newRestart ucloud css restart +func newRestart(ctx *cli.Context) *cobra.Command { + var async *bool + var yes *bool + var instanceID *string + client := cli.NewServiceClient(ctx, uessdk.NewClient) + req := client.NewRestartUESInstanceRequest() + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart UES instance", + Long: "Restart UES instance", + Run: func(cmd *cobra.Command, args []string) { + id := ctx.PickResourceID(*instanceID) + ok, err := ctx.Confirm(*yes, fmt.Sprintf("Are you sure to restart UES instance[%s]?", id)) + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + req.InstanceId = &id + w := ctx.ProgressWriter() + _, err = client.RestartUESInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + text := fmt.Sprintf("ues[%s] is restarting", id) + if *async { + fmt.Fprintln(w, text) + } else { + ctx.PollerTo(w, describeUESInstanceByID(ctx)).Spoll(id, text, []string{STATE_RUNNING, STATE_ABNORMAL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: id, Action: "restart", Status: "Restarting"}) + }, + } + flags := cmd.Flags() + flags.SortFlags = false + instanceID = flags.String("instance-id", "", "Required. Instance ID to restart") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Assign project-id") + req.Region = flags.String("region", ctx.DefaultRegion(), "Optional. Assign region") + req.Zone = flags.String("zone", ctx.DefaultZone(), "Optional. Assign availability zone") + yes = flags.BoolP("yes", "y", false, "Optional. Skip confirmation prompt") + async = flags.Bool("async", false, "Optional. Do not wait for restart to finish") + + command.SetCompletion(cmd, "instance-id", func() []string { + return getInstanceList(ctx, []string{STATE_RUNNING, STATE_ABNORMAL}, *req.ProjectId, *req.Region, "") + }) + + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/css/internal/css/rows.go b/products/css/internal/css/rows.go new file mode 100644 index 0000000000..ec18026934 --- /dev/null +++ b/products/css/internal/css/rows.go @@ -0,0 +1,41 @@ +package css + +// InstanceRow represents a UES instance in list output +type InstanceRow struct { + InstanceID string + InstanceName string + AppName string + AppVersion string + Zone string + State string + NodeCount string + VPCId string + SubnetId string + ChargeType string + CreateTime string + ExpireTime string +} + +// DiskLimitRow represents a disk size limitation in disk-limit output +type DiskLimitRow struct { + DiskType string + MinSizeGB string + MaxSizeGB string +} + +// NodeConfRow represents a node configuration in node-conf output +type NodeConfRow struct { + NodeConf string + CPU string + MemoryGB string + DiskSizeGB string + DiskType string + SecGroup string +} + +// AppVersionRow represents an application version in app-version output +type AppVersionRow struct { + AppName string + AppVersion string + IsMultiZone string +} diff --git a/products/css/internal/css/status.go b/products/css/internal/css/status.go new file mode 100644 index 0000000000..d5ea2f45e7 --- /dev/null +++ b/products/css/internal/css/status.go @@ -0,0 +1,11 @@ +package css + +// UES instance state constants +const ( + STATE_RUNNING = "Running" + STATE_STOPPED = "Stopped" + STATE_CREATING = "Creating" + STATE_DELETING = "Deleting" + STATE_ABNORMAL = "Abnormal" + STATE_RESTARTING = "Restarting" +) diff --git a/products/css/product.go b/products/css/product.go new file mode 100644 index 0000000000..d5b92eee0a --- /dev/null +++ b/products/css/product.go @@ -0,0 +1,21 @@ +package css + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalcss "github.com/ucloud/ucloud-cli/products/css/internal/css" +) + +type product struct{} + +// New returns the css product (registered via hack/gen-products). +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "css", Commands: []string{"css"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalcss.NewCommand(ctx)} +} diff --git a/products/css/product.yaml b/products/css/product.yaml new file mode 100644 index 0000000000..e41ad029e3 --- /dev/null +++ b/products/css/product.yaml @@ -0,0 +1,7 @@ +# products/css/product.yaml — css 产品元数据 +name: css +owners: + - Episkey-G +commands: + - css +enabled: true diff --git a/products/css/testdata/cmdtree.golden b/products/css/testdata/cmdtree.golden new file mode 100644 index 0000000000..e347cce693 --- /dev/null +++ b/products/css/testdata/cmdtree.golden @@ -0,0 +1,78 @@ +ucloud css use=css short=Manage UES (Elasticsearch/OpenSearch) instances +ucloud css app-version use=app-version short=List available UES application versions + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css create use=create short=Create UES instance + flag=app-name short= default=elasticsearch required= + flag=app-version short= default= required=true + flag=async short= default=false required= + flag=business-id short= default= required= + flag=charge-type short= default=Month required= + flag=kibana-disk-conf short= default=CLOUD_RSSD required=true + flag=kibana-node-conf short= default= required=true + flag=name short= default= required=true + flag=node-conf short= default= required=true + flag=node-count short= default=3 required= + flag=node-disk-conf short= default=CLOUD_RSSD required=true + flag=node-disk-size-gb short= default=100 required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=remark short= default= required= + flag=service-passwd short= default= required= + flag=service-username short= default= required= + flag=subnet-id short= default= required=true + flag=vpc-id short= default= required=true + flag=zone short= default= required= +ucloud css delete use=delete short=Delete UES instances + flag=instance-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud css describe use=describe short=Describe UES instance details + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css disk-limit use=disk-limit short=List UES disk size limitations by disk type + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css expand use=expand short=Expand UES instance node count + flag=async short= default=false required= + flag=instance-id short= default= required=true + flag=node-count short= default=0 required=true + flag=node-role short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css list use=list short=List UES instances + flag=limit short= default=30 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css node-conf use=node-conf short=List available UES node configurations + flag=app-version short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css resize use=resize short=Resize UES instance node configuration + flag=async short= default=false required= + flag=force short= default=false required= + flag=instance-id short= default= required=true + flag=node-conf short= default= required= + flag=node-disk-size-gb short= default=0 required= + flag=node-role short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud css restart use=restart short=Restart UES instance + flag=async short= default=false required= + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=yes short=y default=false required= + flag=zone short= default= required= diff --git a/products/css/testdata/completion.golden b/products/css/testdata/completion.golden new file mode 100644 index 0000000000..2b7df5a0cf --- /dev/null +++ b/products/css/testdata/completion.golden @@ -0,0 +1,8 @@ +ucloud css create charge-type static Dynamic,Month,Year +ucloud css delete instance-id dynamic +ucloud css describe instance-id dynamic +ucloud css expand instance-id dynamic +ucloud css expand node-role static compute,coordinating +ucloud css resize instance-id dynamic +ucloud css resize node-role static compute,coordinating,dashboard,kibana,master +ucloud css restart instance-id dynamic From f875775a3ecad5a72e870f86b5dafd9eb0f34b9c Mon Sep 17 00:00:00 2001 From: rocky-ucloud Date: Fri, 10 Jul 2026 16:08:39 +0800 Subject: [PATCH 2/3] Update owner email for css product --- products/css/product.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/products/css/product.yaml b/products/css/product.yaml index e41ad029e3..7e720035ca 100644 --- a/products/css/product.yaml +++ b/products/css/product.yaml @@ -1,7 +1,7 @@ # products/css/product.yaml — css 产品元数据 name: css owners: - - Episkey-G + - rocky.gu@ucloud.cn commands: - css enabled: true From ba65758a451773515c9dbc88f692ba1993f08d4b Mon Sep 17 00:00:00 2001 From: rocky-ucloud Date: Fri, 10 Jul 2026 16:14:02 +0800 Subject: [PATCH 3/3] Update owner email to username format --- products/css/product.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/products/css/product.yaml b/products/css/product.yaml index 7e720035ca..b9689ae2f3 100644 --- a/products/css/product.yaml +++ b/products/css/product.yaml @@ -1,7 +1,7 @@ # products/css/product.yaml — css 产品元数据 name: css owners: - - rocky.gu@ucloud.cn + - rocky-ucloud commands: - css enabled: true