diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 45d9fb2e30..f539d1327c 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -25,6 +25,7 @@ import ( "github.com/ucloud/ucloud-cli/products/udns" "github.com/ucloud/ucloud-cli/products/udpn" "github.com/ucloud/ucloud-cli/products/ufs" + "github.com/ucloud/ucloud-cli/products/ugn" "github.com/ucloud/ucloud-cli/products/uhadoop" "github.com/ucloud/ucloud-cli/products/uhost" "github.com/ucloud/ucloud-cli/products/uk8s" @@ -66,6 +67,7 @@ func registeredProducts() []cli.Product { udns.New(), udpn.New(), ufs.New(), + ugn.New(), uhadoop.New(), uhost.New(), uk8s.New(), diff --git a/go.mod b/go.mod index 10558acde9..e801bcced5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/sirupsen/logrus v1.8.3 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - github.com/ucloud/ucloud-sdk-go v0.22.92 + github.com/ucloud/ucloud-sdk-go v0.22.96 gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 0502359755..3892e1726a 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/ucloud/ucloud-sdk-go v0.22.92 h1:HuT+JQfyEx9OFhluTQ/Zusb6obB+2kJ9OJlU/JREHR4= -github.com/ucloud/ucloud-sdk-go v0.22.92/go.mod h1:dyLmFHmUfgb4RZKYQP9IArlvQ2pxzFthfhwxRzOEPIw= +github.com/ucloud/ucloud-sdk-go v0.22.96 h1:efyqknRMEaoik6lH1g3DqO0XBipz4xhurGAXcQcDc2Q= +github.com/ucloud/ucloud-sdk-go v0.22.96/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/ugn/internal/ugn/bw.go b/products/ugn/internal/ugn/bw.go new file mode 100644 index 0000000000..b164ae6c2a --- /dev/null +++ b/products/ugn/internal/ugn/bw.go @@ -0,0 +1,23 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newBW ucloud ugn bw +func newBW(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "bw", + Short: "List and manipulate ugn bandwidth packages", + Long: "List and manipulate ugn bandwidth packages", + } + + cmd.AddCommand(newBWCreate(ctx)) + cmd.AddCommand(newBWDelete(ctx)) + cmd.AddCommand(newBWList(ctx)) + cmd.AddCommand(newBWModifyBandwidth(ctx)) + + return cmd +} diff --git a/products/ugn/internal/ugn/bw_create.go b/products/ugn/internal/ugn/bw_create.go new file mode 100644 index 0000000000..6b026d2541 --- /dev/null +++ b/products/ugn/internal/ugn/bw_create.go @@ -0,0 +1,66 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBWCreate ucloud ugn bw create +func newBWCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewCreateSimpleUGNBwPackageRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a ugn bandwidth package", + Long: "Create a ugn bandwidth package", + Run: func(c *cobra.Command, args []string) { + _, err := client.CreateSimpleUGNBwPackage(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ugn bw created\n") + ctx.EmitResult(cli.OpResultRow{Action: "create-bw", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + req.BandWidth = flags.Int("bandwidth", 0, "Required. Bandwidth value in Mbps") + req.Path = flags.String("path", "IGP", "Path policy: Delay/IGP/TCO") + req.PayMode = flags.String("pay-mode", "", "Required. Pay mode: FixedBw/Max5/Traffic") + req.ChargeType = flags.String("charge-type", "", "Required. Charge type: Month/Postpay") + req.RegionA = flags.String("region-a", "", "Required. Region A of the bandwidth package") + req.RegionB = flags.String("region-b", "", "Required. Region B of the bandwidth package") + req.Name = flags.String("name", "", "Optional. Bandwidth package name") + req.Qos = flags.String("qos", "Platinum", "Optional. QoS: Diamond/Platinum/Gold") + req.CouponId = flags.String("coupon-id", "", "Optional. Coupon ID") + req.Quantity = flags.Float64("quantity", 1, "Optional. Duration in months, default 1") + + ctx.BindProjectID(cmd, req) + ctx.SetCompletion(cmd, "ugn-id", func() []string { + return getAllUGNIdNames(ctx, *req.ProjectId) + }) + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + command.SetFlagValues(cmd, "path", "Delay", "IGP", "TCO") + command.SetFlagValues(cmd, "pay-mode", "FixedBw", "Max5", "Traffic") + command.SetFlagValues(cmd, "charge-type", "Month", "Postpay") + command.SetFlagValues(cmd, "qos", "Diamond", "Platinum", "Gold") + + cmd.MarkFlagRequired("ugn-id") + cmd.MarkFlagRequired("bandwidth") + cmd.MarkFlagRequired("pay-mode") + cmd.MarkFlagRequired("charge-type") + cmd.MarkFlagRequired("region-a") + cmd.MarkFlagRequired("region-b") + + return cmd +} diff --git a/products/ugn/internal/ugn/bw_delete.go b/products/ugn/internal/ugn/bw_delete.go new file mode 100644 index 0000000000..831d479bec --- /dev/null +++ b/products/ugn/internal/ugn/bw_delete.go @@ -0,0 +1,53 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newBWDelete ucloud ugn bw delete +func newBWDelete(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewDeleteUGNBwPackageRequest() + var yes bool + + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete ugn bandwidth packages", + Long: "Delete ugn bandwidth packages", + Run: func(c *cobra.Command, args []string) { + ok, err := ctx.Confirm(yes, "Are you sure you want to delete the bandwidth package?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + _, err = client.DeleteUGNBwPackage(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ugn bw[%s] deleted\n", *req.BwPackageID) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.BwPackageID, Action: "delete-bw", Status: "Deleted"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.BwPackageID = flags.String("bw-package-id", "", "Required. Resource ID of the bandwidth package to delete") + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Skip the confirmation prompt.") + + cmd.MarkFlagRequired("bw-package-id") + cmd.MarkFlagRequired("ugn-id") + + return cmd +} diff --git a/products/ugn/internal/ugn/bw_list.go b/products/ugn/internal/ugn/bw_list.go new file mode 100644 index 0000000000..1a22ce04b5 --- /dev/null +++ b/products/ugn/internal/ugn/bw_list.go @@ -0,0 +1,83 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// bwRow is the table row for `ucloud ugn bw list`. +type bwRow struct { + ResourceID string + Name string + BandwidthMbps float64 + RegionA string + RegionB string + Path string + QoS string + ChargeType string + CreateTime string + ExpireTime string +} + +// newBWList ucloud ugn bw list +func newBWList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewGetSimpleUGNBwPackagesRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ugn bandwidth packages", + Long: "List ugn bandwidth packages", + Run: func(c *cobra.Command, args []string) { + *req.UGNID = ctx.PickResourceID(*req.UGNID) + resp, err := client.GetSimpleUGNBwPackages(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := make([]bwRow, 0, len(resp.BwPackages)) + for _, bw := range resp.BwPackages { + path := bw.Path + if path == "None" { + path = "IGP" + } + expireTime := "" + if bw.ExpireTime > 0 { + expireTime = common.FormatDate(bw.ExpireTime) + } + rows = append(rows, bwRow{ + ResourceID: bw.PackageID, + Name: bw.Name, + BandwidthMbps: bw.BandWidth, + RegionA: bw.RegionA, + RegionB: bw.RegionB, + Path: path, + QoS: bw.Qos, + ChargeType: bw.PayMode, + CreateTime: common.FormatDate(bw.CreateTime), + ExpireTime: expireTime, + }) + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + ctx.BindOffset(cmd, req) + req.Limit = flags.Int("limit", 50, "Optional. Limit") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + cmd.MarkFlagRequired("ugn-id") + ctx.SetCompletion(cmd, "ugn-id", func() []string { + return getAllUGNIdNames(ctx, *req.ProjectId) + }) + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + + return cmd +} diff --git a/products/ugn/internal/ugn/bw_modify_bandwidth.go b/products/ugn/internal/ugn/bw_modify_bandwidth.go new file mode 100644 index 0000000000..d1c2502cf4 --- /dev/null +++ b/products/ugn/internal/ugn/bw_modify_bandwidth.go @@ -0,0 +1,44 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newBWModifyBandwidth ucloud ugn bw modify-bandwidth +func newBWModifyBandwidth(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewModifyUGNBandwidthRequest() + cmd := &cobra.Command{ + Use: "modify-bandwidth", + Short: "Modify ugn bandwidth", + Long: "Modify ugn bandwidth", + Run: func(c *cobra.Command, args []string) { + _, err := client.ModifyUGNBandwidth(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ugn bw[%s] bandwidth modified to %d\n", *req.PackageID, *req.BandWidth) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.PackageID, Action: "modify-bandwidth", Status: "Modified"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.BandWidth = flags.Int("bandwidth", 0, "Required. New bandwidth value") + req.PackageID = flags.String("package-id", "", "Required. Bandwidth package ID") + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + + cmd.MarkFlagRequired("bandwidth") + cmd.MarkFlagRequired("package-id") + cmd.MarkFlagRequired("ugn-id") + + return cmd +} diff --git a/products/ugn/internal/ugn/cmd.go b/products/ugn/internal/ugn/cmd.go new file mode 100644 index 0000000000..b2be218dd2 --- /dev/null +++ b/products/ugn/internal/ugn/cmd.go @@ -0,0 +1,27 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `ugn` root command. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "ugn", + Short: "List and manipulate ugn instances", + Long: "List and manipulate ugn instances", + } + + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newGet(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newRegion(ctx)) + cmd.AddCommand(newBW(ctx)) + cmd.AddCommand(newNetwork(ctx)) + cmd.AddCommand(newRoute(ctx)) + + return cmd +} diff --git a/products/ugn/internal/ugn/completion.go b/products/ugn/internal/ugn/completion.go new file mode 100644 index 0000000000..557ce5737b --- /dev/null +++ b/products/ugn/internal/ugn/completion.go @@ -0,0 +1,42 @@ +package ugn + +import ( + "fmt" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +func getAllUGNIns(ctx *cli.Context, project string) ([]ugnsdk.UGN, error) { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewListUGNRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + list := make([]ugnsdk.UGN, 0) + for offset, limit := 0, 50; offset <= 1000; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.ListUGN(req) + if err != nil { + return nil, err + } + list = append(list, resp.UGNs...) + if offset+limit >= resp.TotalCount { + break + } + } + return list, nil +} + +func getAllUGNIdNames(ctx *cli.Context, project string) []string { + ugnInsList, err := getAllUGNIns(ctx, project) + if err != nil { + return nil + } + idNameList := []string{} + for _, ugn := range ugnInsList { + idNameList = append(idNameList, fmt.Sprintf("%s/%s", ugn.UGNID, ugn.Name)) + } + return idNameList +} diff --git a/products/ugn/internal/ugn/create.go b/products/ugn/internal/ugn/create.go new file mode 100644 index 0000000000..4151bad730 --- /dev/null +++ b/products/ugn/internal/ugn/create.go @@ -0,0 +1,44 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newCreate ucloud ugn create +func newCreate(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewCreateUGNRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a ugn instance", + Long: "Create a ugn instance", + Run: func(c *cobra.Command, args []string) { + resp, err := client.CreateUGN(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "ugn[%s] created\n", resp.UGNID) + ctx.EmitResult(cli.OpResultRow{ResourceID: resp.UGNID, Action: "create", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.Name = flags.String("name", "", "Required. Name of the ugn instance to create") + req.Remark = flags.String("remark", "", "Optional. Remark") + + ctx.BindProjectID(cmd, req) + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + + cmd.MarkFlagRequired("name") + + return cmd +} diff --git a/products/ugn/internal/ugn/delete.go b/products/ugn/internal/ugn/delete.go new file mode 100644 index 0000000000..746c9d5eab --- /dev/null +++ b/products/ugn/internal/ugn/delete.go @@ -0,0 +1,65 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete ucloud ugn delete +func newDelete(ctx *cli.Context) *cobra.Command { + idNames := []string{} + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewDelUGNRequest() + var yes bool + + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete ugn instances", + Long: "Delete ugn instances", + Run: func(c *cobra.Command, args []string) { + ok, err := ctx.Confirm(yes, "Are you sure you want to delete the ugn instance(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.UGNID = sdk.String(id) + _, err := client.DelUGN(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(ctx.ProgressWriter(), "ugn[%s] deleted\n", idname) + results = append(results, cli.OpResultRow{ResourceID: id, Action: "delete", Status: "Deleted"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "ugn-id", nil, "Required. Resource ID of ugn instances to delete") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Skip the confirmation prompt.") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + cmd.MarkFlagRequired("ugn-id") + command.SetCompletion(cmd, "ugn-id", func() []string { + return getAllUGNIdNames(ctx, *req.ProjectId) + }) + command.SetCompletion(cmd, "project-id", ctx.ProjectList) + + return cmd +} diff --git a/products/ugn/internal/ugn/get.go b/products/ugn/internal/ugn/get.go new file mode 100644 index 0000000000..49b2df4783 --- /dev/null +++ b/products/ugn/internal/ugn/get.go @@ -0,0 +1,232 @@ +package ugn + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// getNetworkRow is the table row for networks in ugn get. +type getNetworkRow struct { + NetworkID string + Name string + OrgName string + Region string + Type string + CreateTime string +} + +// getBwRow is the table row for bw packages in ugn get. +type getBwRow struct { + PackageID string + Name string + BandwidthMbps float64 + RegionA string + RegionB string + Path string + QoS string + PayMode string + CreateTime string + ExpireTime string +} + +// getRouteRow is the table row for routes in ugn get. +type getRouteRow struct { + DstAddr string + NextHopID string + NextHopType string + NextHopRegion string + Priority int + Conflict string + Deny string + Restrict string +} + +// getPolicyRow is the table row for policies in ugn get. +type getPolicyRow struct { + PolicyID string + Name string + Priority int + Direction string + Action string + RoutePriority int + Enabled string + DstAddrs string + SrcAddrs string + CreateTime string +} + +// newGet ucloud ugn get +func newGet(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewDescribeSimpleUGNRequest() + cmd := &cobra.Command{ + Use: "get", + Short: "Show details of one ugn instance", + Long: "Show details of one ugn instance", + Run: func(c *cobra.Command, args []string) { + *req.UGNID = ctx.PickResourceID(*req.UGNID) + resp, err := client.DescribeSimpleUGN(req) + if err != nil { + ctx.HandleError(err) + return + } + + // 基本信息 + basic := []cli.DescribeRow{ + {Attribute: "ResourceID", Content: resp.UGN.UGNID}, + {Attribute: "Name", Content: resp.UGN.Name}, + {Attribute: "Remark", Content: resp.UGN.Remark}, + {Attribute: "CreateTime", Content: common.FormatDate(resp.UGN.CreateTime)}, + {Attribute: "NetworkCount", Content: fmt.Sprintf("%d", resp.UGN.NetworkCount)}, + {Attribute: "BwPackageCount", Content: fmt.Sprintf("%d", resp.UGN.BwPackageCount)}, + } + printDescribe(ctx, basic) + + // 网络实例列表 + fmt.Fprintln(ctx.ProgressWriter()) + fmt.Fprintf(ctx.ProgressWriter(), "Networks (%d):\n", len(resp.Networks)) + if len(resp.Networks) > 0 { + networkRows := make([]getNetworkRow, 0, len(resp.Networks)) + for _, nw := range resp.Networks { + networkRows = append(networkRows, getNetworkRow{ + NetworkID: nw.NetworkID, + Name: nw.Name, + OrgName: nw.OrgName, + Region: nw.Region, + Type: nw.Type, + CreateTime: common.FormatDate(nw.CreateTime), + }) + } + ctx.PrintList(networkRows) + } + + // 带宽包列表 + fmt.Fprintln(ctx.ProgressWriter()) + fmt.Fprintf(ctx.ProgressWriter(), "Bandwidth Packages (%d):\n", len(resp.BwPackages)) + if len(resp.BwPackages) > 0 { + bwRows := make([]getBwRow, 0, len(resp.BwPackages)) + for _, bw := range resp.BwPackages { + expireTime := "" + if bw.ExpireTime > 0 { + expireTime = common.FormatDate(bw.ExpireTime) + } + path := bw.Path + if path == "None" { + path = "IGP" + } + bwRows = append(bwRows, getBwRow{ + PackageID: bw.PackageID, + Name: bw.Name, + BandwidthMbps: bw.BandWidth, + RegionA: bw.RegionA, + RegionB: bw.RegionB, + Path: path, + QoS: bw.Qos, + PayMode: bw.PayMode, + CreateTime: common.FormatDate(bw.CreateTime), + ExpireTime: expireTime, + }) + } + ctx.PrintList(bwRows) + } + + // 路由列表 + fmt.Fprintln(ctx.ProgressWriter()) + fmt.Fprintf(ctx.ProgressWriter(), "Routes (%d):\n", len(resp.Routes)) + if len(resp.Routes) > 0 { + routeRows := make([]getRouteRow, 0, len(resp.Routes)) + for _, r := range resp.Routes { + routeRows = append(routeRows, getRouteRow{ + DstAddr: r.DstAddr, + NextHopID: r.NextHopID, + NextHopType: r.NextHopType, + NextHopRegion: r.NextHopRegion, + Priority: r.Priority, + Conflict: strconv.FormatBool(r.Conflict), + Deny: strconv.FormatBool(r.Deny), + Restrict: strconv.FormatBool(r.Restrict), + }) + } + ctx.PrintList(routeRows) + } + + // 路由策略列表 + fmt.Fprintln(ctx.ProgressWriter()) + fmt.Fprintf(ctx.ProgressWriter(), "Policies (%d):\n", len(resp.Policies)) + if len(resp.Policies) > 0 { + policyRows := make([]getPolicyRow, 0, len(resp.Policies)) + for _, p := range resp.Policies { + dstAddrs := make([]string, 0, len(p.DstNetworks)) + for _, n := range p.DstNetworks { + if len(n.Prefixes) > 0 { + dstAddrs = append(dstAddrs, fmt.Sprintf("%s(%s)", n.NetworkId, strings.Join(n.Prefixes, ","))) + } else { + dstAddrs = append(dstAddrs, n.NetworkId) + } + } + srcAddrs := make([]string, 0, len(p.SrcNetworks)) + for _, n := range p.SrcNetworks { + if len(n.Prefixes) > 0 { + srcAddrs = append(srcAddrs, fmt.Sprintf("%s(%s)", n.NetworkId, strings.Join(n.Prefixes, ","))) + } else { + srcAddrs = append(srcAddrs, n.NetworkId) + } + } + policyRows = append(policyRows, getPolicyRow{ + PolicyID: p.PolicyId, + Name: p.Name, + Priority: p.Priority, + Direction: p.Direction, + Action: p.Action, + RoutePriority: p.RoutePriority, + Enabled: strconv.FormatBool(p.Enabled), + DstAddrs: strings.Join(dstAddrs, ", "), + SrcAddrs: strings.Join(srcAddrs, ", "), + CreateTime: common.FormatDate(p.CreateTime), + }) + } + ctx.PrintList(policyRows) + } + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance to describe") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + cmd.MarkFlagRequired("ugn-id") + ctx.SetCompletion(cmd, "ugn-id", func() []string { + return getAllUGNIdNames(ctx, *req.ProjectId) + }) + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + + return cmd +} + +// printDescribe renders describe rows without column headers in table mode, +// printing each attribute/content pair as an aligned key-value row. +func printDescribe(ctx *cli.Context, rows []cli.DescribeRow) { + if ctx.Format() != cli.OutputTable { + ctx.PrintList(rows) + return + } + maxWidth := 0 + for _, r := range rows { + if len(r.Attribute) > maxWidth { + maxWidth = len(r.Attribute) + } + } + for _, r := range rows { + fmt.Fprintf(ctx.Out(), "%-*s %s\n", maxWidth, r.Attribute, r.Content) + } +} diff --git a/products/ugn/internal/ugn/list.go b/products/ugn/internal/ugn/list.go new file mode 100644 index 0000000000..317bc5a226 --- /dev/null +++ b/products/ugn/internal/ugn/list.go @@ -0,0 +1,49 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newList ucloud ugn list +func newList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewListUGNRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ugn instances", + Long: "List ugn instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.ListUGN(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []UGNRow{} + for _, ugn := range resp.UGNs { + list = append(list, UGNRow{ + ResourceID: ugn.UGNID, + Name: ugn.Name, + Remark: ugn.Remark, + NetworkCount: ugn.NetworkCount, + BwPackageCount: ugn.BwPackageCount, + CreateTime: common.FormatDate(ugn.CreateTime), + }) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindOffset(cmd, req) + req.Limit = flags.Int("limit", 50, "Optional. Limit") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + return cmd +} diff --git a/products/ugn/internal/ugn/network.go b/products/ugn/internal/ugn/network.go new file mode 100644 index 0000000000..e7f5cd1984 --- /dev/null +++ b/products/ugn/internal/ugn/network.go @@ -0,0 +1,21 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newNetwork ucloud ugn network +func newNetwork(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "network", + Short: "Manage ugn network instances", + Long: "Manage ugn network instances", + } + + cmd.AddCommand(newNetworkAttach(ctx)) + cmd.AddCommand(newNetworkDetach(ctx)) + + return cmd +} diff --git a/products/ugn/internal/ugn/network_attach.go b/products/ugn/internal/ugn/network_attach.go new file mode 100644 index 0000000000..84e85660bc --- /dev/null +++ b/products/ugn/internal/ugn/network_attach.go @@ -0,0 +1,72 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newNetworkAttach ucloud ugn network attach +func newNetworkAttach(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewAttachUGNNetworksRequest() + + var networkIDs []string + var networkOrgNames, networkRegions, networkTypes []string + + cmd := &cobra.Command{ + Use: "attach", + Short: "Attach network instances to ugn", + Long: "Attach network instances to ugn", + Run: func(c *cobra.Command, args []string) { + n := len(networkIDs) + if len(networkTypes) != n || len(networkRegions) != n || len(networkOrgNames) != n { + ctx.HandleError(fmt.Errorf( + "network-id(%d), network-type(%d), network-region(%d), network-project-id(%d) must be provided in equal numbers", + n, len(networkTypes), len(networkRegions), len(networkOrgNames))) + return + } + networks := make([]ugnsdk.AttachUGNNetworksParamNetworks, 0, n) + for i, id := range networkIDs { + id = ctx.PickResourceID(id) + networks = append(networks, ugnsdk.AttachUGNNetworksParamNetworks{ + NetworkID: sdk.String(id), + OrgName: sdk.String(networkOrgNames[i]), + Region: sdk.String(networkRegions[i]), + Type: sdk.String(networkTypes[i]), + }) + } + req.Networks = networks + + _, err := client.AttachUGNNetworks(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "networks attached to ugn[%s]\n", *req.UGNID) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.UGNID, Action: "attach-network", Status: "Attached"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&networkIDs, "network-id", nil, "Required. Network IDs, e.g. vnet-xxxxx. Repeatable or comma-separated.") + flags.StringSliceVar(&networkOrgNames, "network-project-id", nil, "Required. Project ID of the networks, one per network-id") + flags.StringSliceVar(&networkRegions, "network-region", nil, "Required. Region of the networks, one per network-id") + flags.StringSliceVar(&networkTypes, "network-type", nil, "Required. Network type, e.g. VPC/UCVR, one per network-id") + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + + cmd.MarkFlagRequired("network-id") + cmd.MarkFlagRequired("network-project-id") + cmd.MarkFlagRequired("network-region") + cmd.MarkFlagRequired("network-type") + cmd.MarkFlagRequired("ugn-id") + + return cmd +} diff --git a/products/ugn/internal/ugn/network_detach.go b/products/ugn/internal/ugn/network_detach.go new file mode 100644 index 0000000000..889f59c830 --- /dev/null +++ b/products/ugn/internal/ugn/network_detach.go @@ -0,0 +1,62 @@ +package ugn + +import ( + "fmt" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newNetworkDetach ucloud ugn network detach +func newNetworkDetach(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewDetachUGNNetworksRequest() + + var networkIDs []string + var yes bool + + cmd := &cobra.Command{ + Use: "detach", + Short: "Detach network instances from ugn", + Long: "Detach network instances from ugn", + Run: func(c *cobra.Command, args []string) { + ok, err := ctx.Confirm(yes, "Are you sure you want to detach the network instance(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + req.UGNID = sdk.String(ctx.PickResourceID(*req.UGNID)) + req.Networks = make([]string, 0, len(networkIDs)) + for _, id := range networkIDs { + req.Networks = append(req.Networks, ctx.PickResourceID(id)) + } + + _, err = client.DetachUGNNetworks(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "networks detached from ugn[%s]\n", *req.UGNID) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.UGNID, Action: "detach-network", Status: "Detached"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&networkIDs, "network-id", nil, "Required. Network IDs, e.g. vnet-xxxxx. Repeatable or comma-separated.") + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Skip the confirmation prompt.") + + cmd.MarkFlagRequired("network-id") + cmd.MarkFlagRequired("ugn-id") + + return cmd +} diff --git a/products/ugn/internal/ugn/region.go b/products/ugn/internal/ugn/region.go new file mode 100644 index 0000000000..38afcef1f0 --- /dev/null +++ b/products/ugn/internal/ugn/region.go @@ -0,0 +1,20 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newRegion ucloud ugn region +func newRegion(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "region", + Short: "List and manipulate ugn regions", + Long: "List and manipulate ugn regions", + } + + cmd.AddCommand(newRegionList(ctx)) + + return cmd +} diff --git a/products/ugn/internal/ugn/region_list.go b/products/ugn/internal/ugn/region_list.go new file mode 100644 index 0000000000..71cdd61b85 --- /dev/null +++ b/products/ugn/internal/ugn/region_list.go @@ -0,0 +1,47 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// regionRow is the table row for `ucloud ugn region list`. +type regionRow struct { + Region string + RegionID string + IsOnline bool + IsOverseas bool +} + +// newRegionList ucloud ugn region list +func newRegionList(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewListUGNRegionsRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List ugn regions", + Long: "List ugn regions", + Run: func(c *cobra.Command, args []string) { + resp, err := client.ListUGNRegions(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := make([]regionRow, 0, len(resp.RegionLIst)) + for _, r := range resp.RegionLIst { + rows = append(rows, regionRow{ + Region: r.Region, + RegionID: r.RegIonId, + IsOnline: r.IsOnline, + IsOverseas: r.IsOverseas, + }) + } + ctx.PrintList(rows) + }, + } + + return cmd +} diff --git a/products/ugn/internal/ugn/route.go b/products/ugn/internal/ugn/route.go new file mode 100644 index 0000000000..71cde471e7 --- /dev/null +++ b/products/ugn/internal/ugn/route.go @@ -0,0 +1,92 @@ +package ugn + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + ugnsdk "github.com/ucloud/ucloud-sdk-go/services/ugn" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// routeRow is the table row for routes in ugn route. +type routeRow struct { + DstAddr string + NextHopID string + NextHopType string + NextHopRegion string + Priority int + Conflict string + Deny string + Restrict string +} + +// newRoute ucloud ugn route +func newRoute(ctx *cli.Context) *cobra.Command { + client := cli.NewServiceClient(ctx, ugnsdk.NewClient) + req := client.NewGetUGNRouteTableRequest() + + cmd := &cobra.Command{ + Use: "route", + Short: "Show route table of one ugn instance", + Long: "Show route table of one ugn instance", + Run: func(c *cobra.Command, args []string) { + *req.UGNID = ctx.PickResourceID(*req.UGNID) + resp, err := client.GetUGNRouteTable(req) + if err != nil { + ctx.HandleError(err) + return + } + + if *req.Type == "Final" { + for _, vr := range resp.VRoutes { + fmt.Fprintf(ctx.ProgressWriter(), "Network %s (%d routes):\n", vr.NetworkId, len(vr.Routes)) + printRouteRows(ctx, vr.Routes) + fmt.Fprintln(ctx.ProgressWriter()) + } + } else { + fmt.Fprintf(ctx.ProgressWriter(), "Route Table (%s, %d routes):\n", *req.Type, len(resp.Routes)) + printRouteRows(ctx, resp.Routes) + } + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.UGNID = flags.String("ugn-id", "", "Required. Resource ID of the ugn instance") + req.Type = flags.String("type", "Final", "Route table type: Origin/Middle/Final") + req.ProjectId = flags.String("project-id", ctx.DefaultProjectID(), "Optional. Project-id, see 'ucloud project list'") + + cmd.MarkFlagRequired("ugn-id") + ctx.SetCompletion(cmd, "ugn-id", func() []string { + return getAllUGNIdNames(ctx, *req.ProjectId) + }) + ctx.SetCompletion(cmd, "project-id", ctx.ProjectList) + command.SetFlagValues(cmd, "type", "Origin", "Middle", "Final") + + return cmd +} + +func printRouteRows(ctx *cli.Context, routes []ugnsdk.SimpleRoute) { + if len(routes) == 0 { + return + } + rows := make([]routeRow, 0, len(routes)) + for _, r := range routes { + rows = append(rows, routeRow{ + DstAddr: r.DstAddr, + NextHopID: r.NextHopID, + NextHopType: r.NextHopType, + NextHopRegion: r.NextHopRegion, + Priority: r.Priority, + Conflict: strconv.FormatBool(r.Conflict), + Deny: strconv.FormatBool(r.Deny), + Restrict: strconv.FormatBool(r.Restrict), + }) + } + ctx.PrintList(rows) +} diff --git a/products/ugn/internal/ugn/rows.go b/products/ugn/internal/ugn/rows.go new file mode 100644 index 0000000000..9c80df6ed3 --- /dev/null +++ b/products/ugn/internal/ugn/rows.go @@ -0,0 +1,17 @@ +package ugn + +import "github.com/ucloud/ucloud-cli/internal/common" + +// UGNRow is the table row for `ucloud ugn list`. +type UGNRow struct { + ResourceID string + Name string + Remark string + NetworkCount int + BwPackageCount int + CreateTime string +} + +func formatUGNCreateTime(t int) string { + return common.FormatDate(t) +} diff --git a/products/ugn/product.go b/products/ugn/product.go new file mode 100644 index 0000000000..1f30a3e129 --- /dev/null +++ b/products/ugn/product.go @@ -0,0 +1,22 @@ +package ugn + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalugn "github.com/ucloud/ucloud-cli/products/ugn/internal/ugn" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "ugn", Commands: []string{"ugn"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalugn.NewCommand(ctx)} +} + +var _ cli.Product = (*product)(nil) diff --git a/products/ugn/product.yaml b/products/ugn/product.yaml new file mode 100644 index 0000000000..b66bd83d0a --- /dev/null +++ b/products/ugn/product.yaml @@ -0,0 +1,6 @@ +name: ugn +owners: + - kyler-67 +commands: + - ugn +enabled: true diff --git a/products/ugn/testdata/cmdtree.golden b/products/ugn/testdata/cmdtree.golden new file mode 100644 index 0000000000..1df7fcbe92 --- /dev/null +++ b/products/ugn/testdata/cmdtree.golden @@ -0,0 +1,60 @@ +ucloud ugn use=ugn short=List and manipulate ugn instances +ucloud ugn bw use=bw short=List and manipulate ugn bandwidth packages +ucloud ugn bw create use=create short=Create a ugn bandwidth package + flag=bandwidth short= default=0 required=true + flag=charge-type short= default= required=true + flag=coupon-id short= default= required= + flag=name short= default= required= + flag=path short= default=IGP required= + flag=pay-mode short= default= required=true + flag=project-id short= default= required= + flag=qos short= default=Platinum required= + flag=quantity short= default=1 required= + flag=region-a short= default= required=true + flag=region-b short= default= required=true + flag=ugn-id short= default= required=true +ucloud ugn bw delete use=delete short=Delete ugn bandwidth packages + flag=bw-package-id short= default= required=true + flag=ugn-id short= default= required=true + flag=yes short=y default=false required= +ucloud ugn bw list use=list short=List ugn bandwidth packages + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=ugn-id short= default= required=true +ucloud ugn bw modify-bandwidth use=modify-bandwidth short=Modify ugn bandwidth + flag=bandwidth short= default=0 required=true + flag=package-id short= default= required=true + flag=ugn-id short= default= required=true +ucloud ugn create use=create short=Create a ugn instance + flag=name short= default= required=true + flag=project-id short= default= required= + flag=remark short= default= required= +ucloud ugn delete use=delete short=Delete ugn instances + flag=project-id short= default= required= + flag=ugn-id short= default=[] required=true + flag=yes short=y default=false required= +ucloud ugn get use=get short=Show details of one ugn instance + flag=project-id short= default= required= + flag=ugn-id short= default= required=true +ucloud ugn list use=list short=List ugn instances + flag=limit short= default=50 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= +ucloud ugn network use=network short=Manage ugn network instances +ucloud ugn network attach use=attach short=Attach network instances to ugn + flag=network-id short= default=[] required=true + flag=network-project-id short= default=[] required=true + flag=network-region short= default=[] required=true + flag=network-type short= default=[] required=true + flag=ugn-id short= default= required=true +ucloud ugn network detach use=detach short=Detach network instances from ugn + flag=network-id short= default=[] required=true + flag=ugn-id short= default= required=true + flag=yes short=y default=false required= +ucloud ugn region use=region short=List and manipulate ugn regions +ucloud ugn region list use=list short=List ugn regions +ucloud ugn route use=route short=Show route table of one ugn instance + flag=project-id short= default= required= + flag=type short= default=Final required= + flag=ugn-id short= default= required=true diff --git a/products/ugn/testdata/completion.golden b/products/ugn/testdata/completion.golden new file mode 100644 index 0000000000..56aadad966 --- /dev/null +++ b/products/ugn/testdata/completion.golden @@ -0,0 +1,16 @@ +ucloud ugn bw create charge-type static Month,Postpay +ucloud ugn bw create path static Delay,IGP,TCO +ucloud ugn bw create pay-mode static FixedBw,Max5,Traffic +ucloud ugn bw create project-id dynamic +ucloud ugn bw create qos static Diamond,Gold,Platinum +ucloud ugn bw create ugn-id dynamic +ucloud ugn bw list project-id dynamic +ucloud ugn bw list ugn-id dynamic +ucloud ugn create project-id dynamic +ucloud ugn delete project-id dynamic +ucloud ugn delete ugn-id dynamic +ucloud ugn get project-id dynamic +ucloud ugn get ugn-id dynamic +ucloud ugn route project-id dynamic +ucloud ugn route type static Final,Middle,Origin +ucloud ugn route ugn-id dynamic