diff --git a/cmd/products.gen.go b/cmd/products.gen.go index 87c5c237ec..45fc5e83eb 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -11,6 +11,7 @@ import ( "github.com/ucloud/ucloud-cli/products/memcache" "github.com/ucloud/ucloud-cli/products/mysql" "github.com/ucloud/ucloud-cli/products/pathx" + "github.com/ucloud/ucloud-cli/products/pgsql" "github.com/ucloud/ucloud-cli/products/redis" "github.com/ucloud/ucloud-cli/products/sharedbw" "github.com/ucloud/ucloud-cli/products/subnet" @@ -40,6 +41,7 @@ func registeredProducts() []cli.Product { memcache.New(), mysql.New(), pathx.New(), + pgsql.New(), redis.New(), sharedbw.New(), subnet.New(), diff --git a/products/pgsql/internal/pgsql/backup.go b/products/pgsql/internal/pgsql/backup.go new file mode 100644 index 0000000000..a512003271 --- /dev/null +++ b/products/pgsql/internal/pgsql/backup.go @@ -0,0 +1,21 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPgsqlBackup ucloud pgsql backup +func newPgsqlBackup(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "backup", + Short: "List and manipulate backups of UPgSQL instances", + Long: "List and manipulate backups of UPgSQL instances", + } + cmd.AddCommand(newBackupList(ctx)) + cmd.AddCommand(newBackupDownload(ctx)) + cmd.AddCommand(newBackupStrategy(ctx)) + cmd.AddCommand(newBackupUpdateStrategy(ctx)) + return cmd +} diff --git a/products/pgsql/internal/pgsql/backup_download.go b/products/pgsql/internal/pgsql/backup_download.go new file mode 100644 index 0000000000..df71da3c4d --- /dev/null +++ b/products/pgsql/internal/pgsql/backup_download.go @@ -0,0 +1,51 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackupDownload ucloud pgsql backup download +func newBackupDownload(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLBackupURLRequest() + cmd := &cobra.Command{ + Use: "download", + Short: "Display download URLs of a UPgSQL backup", + Long: "Display the public and inner download URLs of a UPgSQL backup", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + resp, err := client.GetUPgSQLBackupURL(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintList([]PgsqlBackupURLRow{{ + BackupPath: resp.BackupPath, + InnerBackupPath: resp.InnerBackupPath, + }}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.BackupID = flags.String("backup-id", "", "Required. Backup ID of the backup to download") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("backup-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + command.SetCompletion(cmd, "backup-id", func() []string { + return getBackupIDList(ctx, *req.InstanceID, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/backup_list.go b/products/pgsql/internal/pgsql/backup_list.go new file mode 100644 index 0000000000..27e94ce69e --- /dev/null +++ b/products/pgsql/internal/pgsql/backup_list.go @@ -0,0 +1,80 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + 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" +) + +var pgsqlBackupTypeMap = map[string]int{ + "auto": 1, + "manual": 2, +} +var pgsqlReverseBackupTypeMap = map[int]string{ + 1: "auto", + 2: "manual", + 0: "all", +} + +// newBackupList ucloud pgsql backup list +func newBackupList(ctx *cli.Context) *cobra.Command { + var bpType string + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLBackupRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List backups of a UPgSQL instance", + Long: "List backups of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + if v, ok := pgsqlBackupTypeMap[bpType]; ok { + req.BackupType = sdk.Int(v) + } + resp, err := client.ListUPgSQLBackup(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []PgsqlBackupRow{} + for _, b := range resp.DataSet { + list = append(list, PgsqlBackupRow{ + BackupID: b.BackupID, + BackupName: b.BackupName, + InstanceID: b.InstanceID, + State: b.State, + BackupType: pgsqlReverseBackupTypeMap[b.BackupType], + BackupSize: fmt.Sprintf("%dB", b.BackupSize), + BackupStartTime: common.FormatDateTime(b.BackupStartTime), + BackupEndTime: common.FormatDateTime(b.BackupEndTime), + }) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + flags.StringVar(&bpType, "backup-type", "", "Optional. Backup type. Accept values: auto, manual") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + ctx.BindOffset(cmd, req) + ctx.BindLimit(cmd, req) + + command.SetFlagValues(cmd, "backup-type", "auto", "manual") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/backup_strategy.go b/products/pgsql/internal/pgsql/backup_strategy.go new file mode 100644 index 0000000000..adda81ea4d --- /dev/null +++ b/products/pgsql/internal/pgsql/backup_strategy.go @@ -0,0 +1,50 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackupStrategy ucloud pgsql backup strategy +func newBackupStrategy(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLBackupStrategyRequest() + cmd := &cobra.Command{ + Use: "strategy", + Short: "Display the backup strategy of a UPgSQL instance", + Long: "Display the backup strategy of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + resp, err := client.GetUPgSQLBackupStrategy(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintln(ctx.ProgressWriter(), "BackupStrategy:") + ctx.PrintList([]PgsqlBackupStrategyRow{{ + BackupMethod: resp.BackupMethod, + BackupTimeRange: resp.BackupTimeRange, + BackupWeek: resp.BackupWeek, + }}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/backup_update_strategy.go b/products/pgsql/internal/pgsql/backup_update_strategy.go new file mode 100644 index 0000000000..f301d69f74 --- /dev/null +++ b/products/pgsql/internal/pgsql/backup_update_strategy.go @@ -0,0 +1,49 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newBackupUpdateStrategy ucloud pgsql backup update-strategy +func newBackupUpdateStrategy(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewUpdateUPgSQLBackupStrategyRequest() + cmd := &cobra.Command{ + Use: "update-strategy", + Short: "Update the backup strategy of a UPgSQL instance", + Long: "Update the backup strategy of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + _, err := client.UpdateUPgSQLBackupStrategy(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "backup strategy of pgsql[%s] updated\n", *req.InstanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceID, Action: "update-strategy", Status: "Updated"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.BackupTimeRange = flags.String("backup-time-range", "", "Optional. Auto backup start time range, e.g. (3:00~4:00)") + req.BackupWeek = flags.String("backup-week", "", "Optional. Days of week to start auto backup, e.g. 1,2,3,4,5,6,7") + req.BackupMethod = flags.String("backup-method", "", "Optional. Default backup method") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/client.go b/products/pgsql/internal/pgsql/client.go new file mode 100644 index 0000000000..29b61a139a --- /dev/null +++ b/products/pgsql/internal/pgsql/client.go @@ -0,0 +1,38 @@ +package pgsql + +import ( + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUPgSQLClient returns an authed UPgSQL client whose requests are encoded as +// JSON bodies instead of form-urlencoded. +// +// The UPgSQL gateway cannot unmarshal form-urlencoded string values into Go +// int/bool request fields (e.g. ListUPgSQLParamTemplate.Count), returning +// RetCode 214001 "json: cannot unmarshal string into Go struct field ... of +// type int". The SDK default form encoder serializes *int/*bool as the strings +// "100"/"false", which trips this. Switching to NewJSONEncoder keeps numeric +// and boolean fields as JSON numbers/booleans, which the gateway accepts. +// +// The encoder is swapped per-request via an SDK request handler (runs before +// buildHTTPRequest) using the SAME config+credential the default form encoder +// would use, so signing is unchanged. This covers every typed call that goes +// through this client, including the completion helpers. +// +// Note: this works for AK/SK profiles (Signature lives in the signed JSON +// body). OAuth profiles additionally need the platform cred-header injector to +// strip Signature/PublicKey from a JSON body (it currently only strips form +// bodies) — that is a separate platform-layer change; until then OAuth+pgsql +// remains non-functional (as it is today). +func newUPgSQLClient(ctx *cli.Context) *upgsql.UPgSQLClient { + client := cli.NewServiceClient(ctx, upgsql.NewClient) + _ = client.AddRequestHandler(func(c *sdk.Client, req request.Common) (request.Common, error) { + req.SetEncoder(request.NewJSONEncoder(c.GetConfig(), c.GetCredential())) + return req, nil + }) + return client +} diff --git a/products/pgsql/internal/pgsql/cmd.go b/products/pgsql/internal/pgsql/cmd.go new file mode 100644 index 0000000000..36b66b7e57 --- /dev/null +++ b/products/pgsql/internal/pgsql/cmd.go @@ -0,0 +1,22 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `pgsql` root command and mounts the `db` subtree. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "pgsql", + Short: "Manipulate UPgSQL on UCloud platform", + Long: "Manipulate UPgSQL (UCloud PostgreSQL) on UCloud platform", + } + cmd.AddCommand(newPgsqlDB(ctx)) + cmd.AddCommand(newPgsqlConf(ctx)) + cmd.AddCommand(newPgsqlBackup(ctx)) + cmd.AddCommand(newPgsqlLog(ctx)) + cmd.AddCommand(newPgsqlSupabase(ctx)) + return cmd +} diff --git a/products/pgsql/internal/pgsql/completion.go b/products/pgsql/internal/pgsql/completion.go new file mode 100644 index 0000000000..2332eabc03 --- /dev/null +++ b/products/pgsql/internal/pgsql/completion.go @@ -0,0 +1,203 @@ +package pgsql + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + "github.com/ucloud/ucloud-sdk-go/services/vpc" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +var pgsqlVersionList = []string{"postgresql-10.4", "postgresql-13.4"} + +// getAllVPCIns mirrors products/mysql/internal/mysql/completion.go getAllVPCIns, +// copied here (not imported) so the product stays self-contained per the +// boundary rules (hack/check-product rule1). +func getAllVPCIns(ctx *cli.Context, project, region string) ([]vpc.VPCInfo, error) { + 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, err + } + return resp.DataSet, nil +} + +// getAllVPCIdNames mirrors products/mysql/internal/mysql/completion.go getAllVPCIdNames. +func getAllVPCIdNames(ctx *cli.Context, project, region string) []string { + vpcInsList, err := getAllVPCIns(ctx, project, region) + list := []string{} + if err != nil { + return nil + } + for _, v := range vpcInsList { + list = append(list, fmt.Sprintf("%s/%s", v.VPCId, v.Name)) + } + return list +} + +// getAllSubnets mirrors products/mysql/internal/mysql/completion.go getAllSubnets. +func getAllSubnets(ctx *cli.Context, vpcID, project, region string) ([]vpc.SubnetInfo, error) { + client := cli.NewServiceClient(ctx, vpc.NewClient) + req := client.NewDescribeSubnetRequest() + req.ProjectId = sdk.String(cli.PickResourceID(project)) + req.Region = sdk.String(region) + if vpcID != "" { + req.VPCId = sdk.String(cli.PickResourceID(vpcID)) + } + subnets := []vpc.SubnetInfo{} + 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 { + ctx.HandleError(err) + return nil, err + } + subnets = append(subnets, resp.DataSet...) + if limit+offset >= resp.TotalCount { + break + } + } + return subnets, nil +} + +// getAllSubnetIDNames mirrors products/mysql/internal/mysql/completion.go getAllSubnetIDNames. +func getAllSubnetIDNames(ctx *cli.Context, vpcID, project, region string) []string { + subnets, err := getAllSubnets(ctx, vpcID, project, region) + if err != nil { + return nil + } + list := []string{} + for _, s := range subnets { + list = append(list, fmt.Sprintf("%s/%s", s.SubnetId, s.SubnetName)) + } + return list +} + +// getUPgSQLList returns all UPgSQL instances for the given project/region/zone. +// ListUPgSQLInstance has no Limit/Offset, so a single call returns the full set. +func getUPgSQLList(ctx *cli.Context, project, region, zone string) ([]upgsql.UDBInstanceSet, error) { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLInstanceRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + resp, err := client.ListUPgSQLInstance(req) + if err != nil { + return nil, err + } + return resp.DataSet, nil +} + +// getUPgSQLIDList returns "InstanceID/Name" completion candidates for --instance-id. +func getUPgSQLIDList(ctx *cli.Context, project, region, zone string) []string { + instances, err := getUPgSQLList(ctx, project, region, zone) + if err != nil { + return nil + } + list := []string{} + for _, ins := range instances { + list = append(list, fmt.Sprintf("%s/%s", ins.InstanceID, ins.Name)) + } + return list +} + +// listParamTemplates returns the available UPgSQL param templates for the given +// project/region/zone, paginating via item-count (the API exposes no TotalCount). +func listParamTemplates(ctx *cli.Context, project, region, zone string) ([]upgsql.TemplateGroup, error) { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLParamTemplateRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + list := []upgsql.TemplateGroup{} + + resp, err := client.ListUPgSQLParamTemplate(req) + if err != nil { + return nil, err + } + list = resp.Data + return list, nil +} + +// getDefaultParamGroupID picks a param template for the given DB version via +// ListUPgSQLParamTemplate. The API has no DBVersion filter, so we match by the +// DBVersion field in the returned templates and fall back to the first one. +func getDefaultParamGroupID(ctx *cli.Context, dbVersion, project, region, zone string) (int, error) { + templates, err := listParamTemplates(ctx, project, region, zone) + if err != nil { + return 0, fmt.Errorf("call ListUPgSQLParamTemplate: %w", err) + } + if len(templates) == 0 { + return 0, fmt.Errorf("no param template found in %s/%s", region, zone) + } + for _, t := range templates { + if t.DBVersion == dbVersion { + return t.GroupID, nil + } + } + return templates[0].GroupID, nil +} + +// listParamTemplateIDNames returns "GroupID/GroupName" candidates for --param-group-id. +func listParamTemplateIDNames(ctx *cli.Context, project, region, zone string) []string { + templates, err := listParamTemplates(ctx, project, region, zone) + if err != nil { + return nil + } + list := []string{} + for _, t := range templates { + list = append(list, fmt.Sprintf("%d/%s", t.GroupID, t.GroupName)) + } + return list +} + +// listMachineTypeIDNames returns "ID/Description" candidates for --machine-type. +func listMachineTypeIDNames(ctx *cli.Context, project, region, zone string) []string { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLMachineTypeRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + resp, err := client.ListUPgSQLMachineType(req) + if err != nil { + return nil + } + list := []string{} + for _, mt := range resp.DataSet { + list = append(list, fmt.Sprintf("%s/%s", mt.ID, mt.Description)) + } + return list +} + +// getBackupIDList returns "BackupID/BackupName" candidates for --backup-id, +// paginating via TotalCount (ListUPgSQLBackup exposes TotalCount). +func getBackupIDList(ctx *cli.Context, instanceID, project, region, zone string) []string { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLBackupRequest() + req.ProjectId = sdk.String(project) + req.Region = sdk.String(region) + req.Zone = sdk.String(zone) + req.InstanceID = sdk.String(instanceID) + list := []string{} + for limit, offset := 100, 0; ; offset += limit { + req.Limit = sdk.Int(limit) + req.Offset = sdk.Int(offset) + resp, err := client.ListUPgSQLBackup(req) + if err != nil { + return nil + } + for _, b := range resp.DataSet { + list = append(list, fmt.Sprintf("%s/%s", b.BackupID, b.BackupName)) + } + if offset+limit >= resp.TotalCount { + break + } + } + return list +} diff --git a/products/pgsql/internal/pgsql/conf.go b/products/pgsql/internal/pgsql/conf.go new file mode 100644 index 0000000000..57952faeea --- /dev/null +++ b/products/pgsql/internal/pgsql/conf.go @@ -0,0 +1,23 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPgsqlConf ucloud pgsql conf +func newPgsqlConf(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "conf", + Short: "List and manipulate parameter templates of UPgSQL instances", + Long: "List and manipulate parameter templates of UPgSQL instances", + } + cmd.AddCommand(newConfList(ctx)) + cmd.AddCommand(newConfDescribe(ctx)) + cmd.AddCommand(newConfCreate(ctx)) + cmd.AddCommand(newConfDelete(ctx)) + cmd.AddCommand(newConfDownload(ctx)) + cmd.AddCommand(newConfUpload(ctx)) + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_create.go b/products/pgsql/internal/pgsql/conf_create.go new file mode 100644 index 0000000000..9a259b6e78 --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_create.go @@ -0,0 +1,62 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newConfCreate ucloud pgsql conf create +func newConfCreate(ctx *cli.Context) *cobra.Command { + var srcConfID string + client := newUPgSQLClient(ctx) + req := client.NewCreateUPgSQLParamTemplateRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a UPgSQL parameter template from a base template", + Long: "Create a UPgSQL parameter template from a base template", + Run: func(c *cobra.Command, args []string) { + id, err := strconv.Atoi(ctx.PickResourceID(srcConfID)) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid src-conf-id: %w", err)) + return + } + req.SrcGroupID = sdk.Int(id) + resp, err := client.CreateUPgSQLParamTemplate(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "conf[%d] created\n", resp.GroupID) + ctx.EmitResult(cli.OpResultRow{ResourceID: strconv.Itoa(resp.GroupID), Action: "create", Status: "Created"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.GroupName = flags.String("name", "", "Required. Name of the parameter template") + flags.StringVar(&srcConfID, "src-conf-id", "", "Required. Group ID of the base template to clone from") + req.DBVersion = flags.String("db-version", "", "Required. DB version. Options: postgresql-10.4, postgresql-13.4") + req.Description = flags.String("description", "", "Optional. Description of the parameter template") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "db-version", pgsqlVersionList...) + command.SetCompletion(cmd, "src-conf-id", func() []string { + return listParamTemplateIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("src-conf-id") + cmd.MarkFlagRequired("db-version") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_delete.go b/products/pgsql/internal/pgsql/conf_delete.go new file mode 100644 index 0000000000..4c047731b4 --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_delete.go @@ -0,0 +1,55 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newConfDelete ucloud pgsql conf delete +func newConfDelete(ctx *cli.Context) *cobra.Command { + var confID string + client := newUPgSQLClient(ctx) + req := client.NewDeleteUPgSQLParamTemplateRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a UPgSQL parameter template", + Long: "Delete a UPgSQL parameter template", + Run: func(c *cobra.Command, args []string) { + id, err := strconv.Atoi(ctx.PickResourceID(confID)) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid conf-id: %w", err)) + return + } + req.GroupID = sdk.Int(id) + _, err = client.DeleteUPgSQLParamTemplate(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "conf[%s] deleted\n", confID) + ctx.EmitResult(cli.OpResultRow{ResourceID: strconv.Itoa(id), Action: "delete", Status: "Deleted"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&confID, "conf-id", "", "Required. Group ID of the parameter template to delete") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("conf-id") + command.SetCompletion(cmd, "conf-id", func() []string { + return listParamTemplateIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_describe.go b/products/pgsql/internal/pgsql/conf_describe.go new file mode 100644 index 0000000000..9e5f8474fe --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_describe.go @@ -0,0 +1,92 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newConfDescribe ucloud pgsql conf describe +func newConfDescribe(ctx *cli.Context) *cobra.Command { + var confID string + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLParamTemplateRequest() + cmd := &cobra.Command{ + Use: "describe", + Short: "Display details of a UPgSQL parameter template", + Long: "Display details of a UPgSQL parameter template", + Run: func(c *cobra.Command, args []string) { + id, err := strconv.Atoi(ctx.PickResourceID(confID)) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid conf-id: %w", err)) + return + } + req.GroupID = sdk.Int(id) + resp, err := client.GetUPgSQLParamTemplate(req) + if err != nil { + ctx.HandleError(err) + return + } + + // Template metadata comes from ListUPgSQLParamTemplate (GetUPgSQLParamTemplate + // returns only the param list). Fetch and filter by GroupID. + templates, err := listParamTemplates(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + if err != nil { + ctx.HandleError(err) + return + } + var group upgsql.TemplateGroup + for _, t := range templates { + if t.GroupID == id { + group = t + break + } + } + attrs := []cli.DescribeRow{ + {Attribute: "GroupID", Content: strconv.Itoa(group.GroupID)}, + {Attribute: "GroupName", Content: group.GroupName}, + {Attribute: "DBVersion", Content: group.DBVersion}, + {Attribute: "Description", Content: group.Description}, + {Attribute: "Modifiable", Content: strconv.FormatBool(group.Modifiable)}, + } + fmt.Fprintln(ctx.ProgressWriter(), "Attributes:") + ctx.PrintList(attrs) + + params := []PgsqlConfParamRow{} + for _, p := range resp.Data { + if p.Key == "" { + continue + } + params = append(params, PgsqlConfParamRow{ + Key: p.Key, + Value: p.Value, + Modifiable: p.Modifiable, + }) + } + fmt.Fprintln(ctx.ProgressWriter(), "\nParameters:") + ctx.PrintList(params) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&confID, "conf-id", "", "Required. Group ID of the parameter template to describe") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("conf-id") + command.SetCompletion(cmd, "conf-id", func() []string { + return listParamTemplateIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_download.go b/products/pgsql/internal/pgsql/conf_download.go new file mode 100644 index 0000000000..a0d46bd263 --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_download.go @@ -0,0 +1,54 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newConfDownload ucloud pgsql conf download +func newConfDownload(ctx *cli.Context) *cobra.Command { + var confID string + client := newUPgSQLClient(ctx) + req := client.NewDownloadUPgSQLParamTemplateRequest() + cmd := &cobra.Command{ + Use: "download", + Short: "Download a UPgSQL parameter template (base64 content)", + Long: "Download a UPgSQL parameter template and print its base64 content to stdout", + Run: func(c *cobra.Command, args []string) { + id, err := strconv.Atoi(ctx.PickResourceID(confID)) + if err != nil { + ctx.HandleError(err) + return + } + req.GroupID = sdk.Int(id) + resp, err := client.DownloadUPgSQLParamTemplate(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprint(ctx.Out(), resp.Content) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&confID, "conf-id", "", "Required. Group ID of the parameter template to download") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("conf-id") + command.SetCompletion(cmd, "conf-id", func() []string { + return listParamTemplateIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_list.go b/products/pgsql/internal/pgsql/conf_list.go new file mode 100644 index 0000000000..19af1efa1f --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_list.go @@ -0,0 +1,45 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newConfList ucloud pgsql conf list +func newConfList(ctx *cli.Context) *cobra.Command { + var common request.CommonBase + cmd := &cobra.Command{ + Use: "list", + Short: "List UPgSQL parameter templates", + Long: "List UPgSQL parameter templates", + Run: func(c *cobra.Command, args []string) { + templates, err := listParamTemplates(ctx, common.GetProjectId(), common.GetRegion(), common.GetZone()) + if err != nil { + ctx.HandleError(err) + return + } + rows := []PgsqlConfRow{} + for _, t := range templates { + rows = append(rows, PgsqlConfRow{ + GroupID: t.GroupID, + GroupName: t.GroupName, + DBVersion: t.DBVersion, + Description: t.Description, + Modifiable: t.Modifiable, + }) + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindProjectID(cmd, &common) + ctx.BindRegion(cmd, &common) + ctx.BindZone(cmd, &common) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/conf_upload.go b/products/pgsql/internal/pgsql/conf_upload.go new file mode 100644 index 0000000000..d4dec6720d --- /dev/null +++ b/products/pgsql/internal/pgsql/conf_upload.go @@ -0,0 +1,65 @@ +package pgsql + +import ( + "encoding/base64" + "fmt" + "strconv" + + "github.com/spf13/cobra" + + 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" +) + +// newConfUpload ucloud pgsql conf upload +func newConfUpload(ctx *cli.Context) *cobra.Command { + var file string + client := newUPgSQLClient(ctx) + req := client.NewUploadUPgSQLParamTemplateRequest() + cmd := &cobra.Command{ + Use: "upload", + Short: "Create a UPgSQL parameter template by uploading a local config file", + Long: "Create a UPgSQL parameter template by uploading a local config file", + Run: func(c *cobra.Command, args []string) { + content, err := cli.ReadFile(file) + if err != nil { + ctx.HandleError(err) + return + } + req.Content = sdk.String(base64.StdEncoding.EncodeToString([]byte(content))) + resp, err := client.UploadUPgSQLParamTemplate(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "conf[%d] uploaded\n", resp.GroupID) + ctx.EmitResult(cli.OpResultRow{ResourceID: strconv.Itoa(resp.GroupID), Action: "upload", Status: "Uploaded"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&file, "conf-file", "", "Required. Path of the local configuration file") + req.GroupName = flags.String("name", "", "Required. Name of the parameter template") + req.DBVersion = flags.String("db-version", "", "Required. DB version. Options: postgresql-10.4, postgresql-13.4") + req.Description = flags.String("description", "", "Optional. Description of the parameter template") + req.ParamGroupType = flags.String("param-group-type", "", "Optional. Parameter group type") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "db-version", pgsqlVersionList...) + command.SetCompletion(cmd, "conf-file", func() []string { + return common.GetFileList("") + }) + + cmd.MarkFlagRequired("conf-file") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("db-version") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/create.go b/products/pgsql/internal/pgsql/create.go new file mode 100644 index 0000000000..f5fbd6f0b0 --- /dev/null +++ b/products/pgsql/internal/pgsql/create.go @@ -0,0 +1,121 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreate ucloud pgsql db create +func newCreate(ctx *cli.Context) *cobra.Command { + var paramGroupID int + var async bool + var password string + + client := newUPgSQLClient(ctx) + req := client.NewCreateUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "create", + Short: "Create a UPgSQL instance", + Long: "Create a UPgSQL instance on UCloud platform", + Run: func(c *cobra.Command, args []string) { + if len(*req.Name) < 6 { + ctx.HandleError(fmt.Errorf("name must be at least 6 characters")) + return + } + if password == "" { + ctx.HandleError(fmt.Errorf("admin password is required")) + return + } + + // ParamGroupID: user-provided wins; otherwise auto-fetch a default template. + if c.Flags().Changed("param-group-id") { + req.ParamGroupID = sdk.Int(paramGroupID) + } else { + id, err := getDefaultParamGroupID(ctx, *req.DBVersion, req.GetProjectId(), req.GetRegion(), req.GetZone()) + if err != nil { + ctx.HandleError(err) + return + } + req.ParamGroupID = sdk.Int(id) + } + + // VPCID/SubnetID accept "id/name" form; pick the id. + *req.VPCID = ctx.PickResourceID(*req.VPCID) + *req.SubnetID = ctx.PickResourceID(*req.SubnetID) + req.AdminPassword = sdk.String(password) + + resp, err := client.CreateUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + + instanceID := resp.InstanceID + if instanceID == "" { + ctx.HandleError(fmt.Errorf("empty InstanceID in response")) + return + } + + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "pgsql[%s] is initializing\n", instanceID) + } else { + text := fmt.Sprintf("pgsql[%s] is initializing", instanceID) + ctx.PollerTo(w, describePgsqlByID(ctx)).Spoll(instanceID, text, []string{PGSQL_RUNNING, PGSQL_INIT_FAILED, PGSQL_START_FAILED}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "create", Status: "Initializing"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + // Required flags + req.Name = flags.String("name", "", "Required. Instance name, at least 6 characters") + flags.StringVar(&password, "password", "", "Required. Admin password") + req.DBVersion = flags.String("version", "", "Required. DB version. Options: postgresql-10.4, postgresql-13.4") + req.MachineType = flags.String("machine-type", "", "Required. Machine type ID, e.g. o.pgsql2m.medium. See 'ucloud pgsql db list-machine-type'") + req.VPCID = flags.String("vpc-id", "", "Required. VPC ID. See 'ucloud vpc list'") + req.SubnetID = flags.String("subnet-id", "", "Required. Subnet ID. See 'ucloud subnet list'") + + // Optional flags + flags.IntVar(¶mGroupID, "param-group-id", 0, "Optional. Param group ID. Auto-fetched if omitted. See 'ucloud pgsql conf list'") + req.DiskSpace = flags.String("disk-size-gb", "100", "Optional. Disk size (GiB), at least 20, default 100") + req.Port = flags.Int("port", 5432, "Optional. Port, default 5432") + req.InstanceMode = flags.String("mode", "Normal", "Optional. Normal / HA") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for creation to finish") + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "version", pgsqlVersionList...) + command.SetFlagValues(cmd, "mode", "Normal", "HA") + 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()) + }) + command.SetCompletion(cmd, "param-group-id", func() []string { + return listParamTemplateIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + command.SetCompletion(cmd, "machine-type", func() []string { + return listMachineTypeIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("password") + cmd.MarkFlagRequired("version") + cmd.MarkFlagRequired("machine-type") + cmd.MarkFlagRequired("vpc-id") + cmd.MarkFlagRequired("subnet-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/create_readonly.go b/products/pgsql/internal/pgsql/create_readonly.go new file mode 100644 index 0000000000..5925a86027 --- /dev/null +++ b/products/pgsql/internal/pgsql/create_readonly.go @@ -0,0 +1,84 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newCreateReadonly ucloud pgsql db create-readonly +func newCreateReadonly(ctx *cli.Context) *cobra.Command { + var async bool + client := newUPgSQLClient(ctx) + req := client.NewCreateUPgSQLReadonlyRequest() + cmd := &cobra.Command{ + Use: "create-readonly", + Short: "Create a readonly replica for a UPgSQL instance", + Long: "Create a readonly replica synchronizing from a source UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.SrcInstanceID = ctx.PickResourceID(*req.SrcInstanceID) + if c.Flags().Changed("vpc-id") { + *req.VPCID = ctx.PickResourceID(*req.VPCID) + } + if c.Flags().Changed("subnet-id") { + *req.SubnetID = ctx.PickResourceID(*req.SubnetID) + } + resp, err := client.CreateUPgSQLReadonly(req) + if err != nil { + ctx.HandleError(err) + return + } + instanceID := resp.InstanceID + if instanceID == "" { + ctx.HandleError(fmt.Errorf("empty InstanceID in response")) + return + } + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "pgsql[%s] is initializing\n", instanceID) + } else { + text := fmt.Sprintf("pgsql[%s] is initializing", instanceID) + ctx.PollerTo(w, describePgsqlByID(ctx)).Spoll(instanceID, text, []string{PGSQL_RUNNING, PGSQL_INIT_FAILED, PGSQL_START_FAILED}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "create-readonly", Status: "Initializing"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.Name = flags.String("name", "", "Required. Name of the readonly replica") + req.SrcInstanceID = flags.String("src-instance-id", "", "Required. Resource ID of the source UPgSQL instance") + req.MachineType = flags.String("machine-type", "", "Required. Machine type ID, e.g. o.pgsql2m.medium. See 'ucloud pgsql db list-machine-type'") + req.DiskSpace = flags.Int("disk-size-gb", 0, "Required. Disk space (GiB)") + req.Port = flags.Int("port", 5432, "Optional. Port of the readonly replica, default 5432") + req.VPCID = flags.String("vpc-id", "", "Optional. VPC ID. Defaults to the source instance's VPC") + req.SubnetID = flags.String("subnet-id", "", "Optional. Subnet ID. Defaults to the source instance's subnet") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetCompletion(cmd, "src-instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + 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()) + }) + command.SetCompletion(cmd, "machine-type", func() []string { + return listMachineTypeIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("src-instance-id") + cmd.MarkFlagRequired("machine-type") + cmd.MarkFlagRequired("disk-size-gb") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/db.go b/products/pgsql/internal/pgsql/db.go new file mode 100644 index 0000000000..3540322bf9 --- /dev/null +++ b/products/pgsql/internal/pgsql/db.go @@ -0,0 +1,36 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPgsqlDB ucloud pgsql db +func newPgsqlDB(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "db", + Short: "Manage UPgSQL instances", + Long: "Manage UPgSQL instances", + } + + cmd.AddCommand(newList(ctx)) + cmd.AddCommand(newGet(ctx)) + cmd.AddCommand(newCreate(ctx)) + cmd.AddCommand(newDelete(ctx)) + cmd.AddCommand(newStart(ctx)) + cmd.AddCommand(newStop(ctx)) + cmd.AddCommand(newRestart(ctx)) + cmd.AddCommand(newUpgrade(ctx)) + cmd.AddCommand(newListMachineType(ctx)) + cmd.AddCommand(newListVersion(ctx)) + cmd.AddCommand(newCreateReadonly(ctx)) + cmd.AddCommand(newStopCreatingReadonly(ctx)) + cmd.AddCommand(newUpdateName(ctx)) + cmd.AddCommand(newUpdateRemark(ctx)) + cmd.AddCommand(newResetPassword(ctx)) + cmd.AddCommand(newPrice(ctx)) + cmd.AddCommand(newUpgradePrice(ctx)) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/delete.go b/products/pgsql/internal/pgsql/delete.go new file mode 100644 index 0000000000..dcdac3429a --- /dev/null +++ b/products/pgsql/internal/pgsql/delete.go @@ -0,0 +1,77 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newDelete ucloud pgsql db delete +func newDelete(ctx *cli.Context) *cobra.Command { + var idNames []string + var yes bool + client := newUPgSQLClient(ctx) + req := client.NewDeleteUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete UPgSQL instances by instance-id", + Long: "Delete UPgSQL instances by instance-id", + Run: func(c *cobra.Command, args []string) { + ok, err := ctx.Confirm(yes, "Are you sure you want to delete the pgsql instance(s)?") + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + any, err := describePgsqlByID(ctx)(id, nil) + if err != nil { + ctx.HandleError(err) + continue + } + req.InstanceID = &id + ins, ok := any.(*upgsql.UDBInstance) + if ok && ins.State == PGSQL_RUNNING { + stopReq := client.NewStopUPgSQLInstanceRequest() + stopReq.ProjectId = req.ProjectId + stopReq.Region = req.Region + stopReq.Zone = req.Zone + stopReq.InstanceID = req.InstanceID + stopPgsqlIns(ctx, stopReq, false, w) + } + _, err = client.DeleteUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + fmt.Fprintf(w, "pgsql[%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, "instance-id", nil, "Required. Resource ID of UPgSQL instances to delete") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation.") + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + return cmd +} diff --git a/products/pgsql/internal/pgsql/get.go b/products/pgsql/internal/pgsql/get.go new file mode 100644 index 0000000000..6e8f7f9e3a --- /dev/null +++ b/products/pgsql/internal/pgsql/get.go @@ -0,0 +1,78 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/internal/common" + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newGet ucloud pgsql db get +func newGet(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "get", + Short: "Display details of a UPgSQL instance", + Long: "Display details of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + resp, err := client.GetUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + ins := resp.DataSet + if ins.InstanceID == "" { + ctx.HandleError(fmt.Errorf("pgsql[%s] may not exist", *req.InstanceID)) + return + } + attrs := []cli.DescribeRow{ + {Attribute: "InstanceID", Content: ins.InstanceID}, + {Attribute: "Name", Content: ins.Name}, + {Attribute: "State", Content: ins.State}, + {Attribute: "Zone", Content: ins.Zone}, + {Attribute: "BackupZone", Content: ins.BackupZone}, + {Attribute: "DBVersion", Content: ins.DBVersion}, + {Attribute: "InstanceMode", Content: ins.InstanceMode}, + {Attribute: "AdminUser", Content: ins.AdminUser}, + {Attribute: "IP", Content: ins.IP}, + {Attribute: "Port", Content: strconv.Itoa(ins.Port)}, + {Attribute: "VPCID", Content: ins.VPCID}, + {Attribute: "SubnetID", Content: ins.SubnetID}, + {Attribute: "ParamGroupID", Content: strconv.Itoa(ins.ParamGroupID)}, + {Attribute: "MemoryLimit", Content: fmt.Sprintf("%dMB", ins.MemoryLimit)}, + {Attribute: "DiskSpace", Content: fmt.Sprintf("%dGB", ins.DiskSpace)}, + {Attribute: "DiskUsedSize", Content: fmt.Sprintf("%.2fGB", ins.DiskUsedSize)}, + {Attribute: "Remark", Content: ins.Remark}, + {Attribute: "BackupCount", Content: strconv.Itoa(ins.BackupCount)}, + {Attribute: "BackupBeginTime", Content: strconv.Itoa(ins.BackupBeginTime)}, + {Attribute: "BackupDate", Content: ins.BackupDate}, + {Attribute: "CreateTime", Content: common.FormatDateTime(ins.CreateTime)}, + {Attribute: "ModifyTime", Content: common.FormatDateTime(ins.ModifyTime)}, + {Attribute: "ExpiredTime", Content: common.FormatDateTime(ins.ExpiredTime)}, + } + fmt.Fprintln(ctx.ProgressWriter(), "Attributes:") + ctx.PrintList(attrs) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/list.go b/products/pgsql/internal/pgsql/list.go new file mode 100644 index 0000000000..bdd5f86c54 --- /dev/null +++ b/products/pgsql/internal/pgsql/list.go @@ -0,0 +1,81 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newList ucloud pgsql db list +func newList(ctx *cli.Context) *cobra.Command { + var instanceID string + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List UPgSQL instances", + Long: "List UPgSQL instances", + Run: func(c *cobra.Command, args []string) { + resp, err := client.ListUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []PgsqlInstanceRow{} + for _, ins := range resp.DataSet { + if instanceID != "" && ins.InstanceID != instanceID { + continue + } + list = append(list, toInstanceRow(ins)) + for _, slave := range ins.DataSet { + list = append(list, toInstanceRow(upgsql.UDBInstanceSet{ + Zone: slave.Zone, + InstanceID: slave.InstanceID, + Name: slave.Name, + DBVersion: slave.DBVersion, + InstanceMode: slave.InstanceMode, + State: slave.State, + VPCID: slave.VPCID, + SubnetID: slave.SubnetID, + IP: slave.IP, + })) + } + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&instanceID, "instance-id", "", "Optional. List the specified UPgSQL instance only") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} + +// toInstanceRow maps an SDK UDBInstanceSet to a PgsqlInstanceRow. role is non-empty +// for readonly slaves (prefixed with ⮭ to indent under the master row). +func toInstanceRow(ins upgsql.UDBInstanceSet) PgsqlInstanceRow { + row := PgsqlInstanceRow{ + Name: ins.Name, + InstanceID: ins.InstanceID, + Zone: ins.Zone, + State: ins.State, + IP: ins.IP, + VPC: ins.VPCID, + Subnet: ins.SubnetID, + InstanceMode: ins.InstanceMode, + DBVersion: ins.DBVersion, + } + return row +} diff --git a/products/pgsql/internal/pgsql/list_machine_type.go b/products/pgsql/internal/pgsql/list_machine_type.go new file mode 100644 index 0000000000..4effc4d606 --- /dev/null +++ b/products/pgsql/internal/pgsql/list_machine_type.go @@ -0,0 +1,45 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newListMachineType ucloud pgsql db list-machine-type +func newListMachineType(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLMachineTypeRequest() + cmd := &cobra.Command{ + Use: "list-machine-type", + Short: "List available UPgSQL machine types", + Long: "List available UPgSQL machine types via ListUPgSQLMachineType API", + Run: func(c *cobra.Command, args []string) { + resp, err := client.ListUPgSQLMachineType(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := []PgsqlMachineTypeRow{} + for _, mt := range resp.DataSet { + rows = append(rows, PgsqlMachineTypeRow{ + ID: mt.ID, + Description: mt.Description, + Cpu: mt.Cpu, + Memory: mt.Memory, + Os: mt.Os, + }) + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/list_version.go b/products/pgsql/internal/pgsql/list_version.go new file mode 100644 index 0000000000..70ac39225d --- /dev/null +++ b/products/pgsql/internal/pgsql/list_version.go @@ -0,0 +1,42 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newListVersion ucloud pgsql db list-version +func newListVersion(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLVersionRequest() + cmd := &cobra.Command{ + Use: "list-version", + Short: "List available UPgSQL versions", + Long: "List available UPgSQL versions via ListUPgSQLVersion API", + Run: func(c *cobra.Command, args []string) { + resp, err := client.ListUPgSQLVersion(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := []PgsqlVersionRow{} + for _, v := range resp.DataSet { + rows = append(rows, PgsqlVersionRow{ + DBVersion: v.DBVersion, + Available: v.Available, + }) + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/log.go b/products/pgsql/internal/pgsql/log.go new file mode 100644 index 0000000000..0d9575954f --- /dev/null +++ b/products/pgsql/internal/pgsql/log.go @@ -0,0 +1,19 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPgsqlLog ucloud pgsql log +func newPgsqlLog(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "log", + Short: "List and back up logs of UPgSQL instances", + Long: "List and back up logs of UPgSQL instances", + } + cmd.AddCommand(newLogList(ctx)) + cmd.AddCommand(newLogBackup(ctx)) + return cmd +} diff --git a/products/pgsql/internal/pgsql/log_backup.go b/products/pgsql/internal/pgsql/log_backup.go new file mode 100644 index 0000000000..3e931bf756 --- /dev/null +++ b/products/pgsql/internal/pgsql/log_backup.go @@ -0,0 +1,78 @@ +package pgsql + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + 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" +) + +var pgsqlLogTypeList = []string{"slow", "error"} + +// newLogBackup ucloud pgsql log backup +func newLogBackup(ctx *cli.Context) *cobra.Command { + var beginTime, endTime string + client := newUPgSQLClient(ctx) + req := client.NewBackupUPgSQLLogRequest() + cmd := &cobra.Command{ + Use: "backup", + Short: "Back up the log package of a UPgSQL instance", + Long: "Back up the slow/error log package of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + if beginTime != "" { + bt, err := time.Parse(common.DateTimeLayout, beginTime) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid begin-time (use %s): %w", common.DateTimeLayout, err)) + return + } + req.BeginTime = sdk.Int(int(bt.Unix())) + } + if endTime != "" { + et, err := time.Parse(common.DateTimeLayout, endTime) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid end-time (use %s): %w", common.DateTimeLayout, err)) + return + } + req.EndTime = sdk.Int(int(et.Unix())) + } + _, err := client.BackupUPgSQLLog(req) + if err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "log of pgsql[%s] backuped\n", *req.InstanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceID, Action: "log-backup", Status: "Backuped"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.BackupName = flags.String("name", "", "Required. Name of the exported backup file") + req.BackupFile = flags.String("backup-file", "", "Required. Name of the log query result file") + req.LogType = flags.String("log-type", "", "Optional. Log type. Accept values: slow, error") + flags.StringVar(&beginTime, "begin-time", "", "Optional. Log begin time, e.g. 2019-01-02/15:04:05") + flags.StringVar(&endTime, "end-time", "", "Optional. Log end time, e.g. 2019-01-02/15:04:05") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "log-type", pgsqlLogTypeList...) + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("name") + cmd.MarkFlagRequired("backup-file") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/log_list.go b/products/pgsql/internal/pgsql/log_list.go new file mode 100644 index 0000000000..5606e335f6 --- /dev/null +++ b/products/pgsql/internal/pgsql/log_list.go @@ -0,0 +1,75 @@ +package pgsql + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + + 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" +) + +// newLogList ucloud pgsql log list +func newLogList(ctx *cli.Context) *cobra.Command { + var beginTime, endTime string + client := newUPgSQLClient(ctx) + req := client.NewListUPgSQLLogRequest() + cmd := &cobra.Command{ + Use: "list", + Short: "List logs of a UPgSQL instance within a time range", + Long: "List logs of a UPgSQL instance within a time range", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + bt, err := time.Parse(common.DateTimeLayout, beginTime) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid begin-time (use %s): %w", common.DateTimeLayout, err)) + return + } + req.BeginTime = sdk.Int(int(bt.Unix())) + et, err := time.Parse(common.DateTimeLayout, endTime) + if err != nil { + ctx.HandleError(fmt.Errorf("invalid end-time (use %s): %w", common.DateTimeLayout, err)) + return + } + req.EndTime = sdk.Int(int(et.Unix())) + resp, err := client.ListUPgSQLLog(req) + if err != nil { + ctx.HandleError(err) + return + } + list := []PgsqlLogRow{} + for _, l := range resp.DataSet { + list = append(list, PgsqlLogRow{ + Name: l.Name, + Size: fmt.Sprintf("%dB", l.Size), + BeginTime: common.FormatDateTime(l.BeginTime), + EndTime: common.FormatDateTime(l.EndTime), + }) + } + ctx.PrintList(list) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + flags.StringVar(&beginTime, "begin-time", "", "Required. Begin time, e.g. 2019-01-02/15:04:05") + flags.StringVar(&endTime, "end-time", "", "Required. End time, e.g. 2019-01-02/15:04:05") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("begin-time") + cmd.MarkFlagRequired("end-time") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/poll.go b/products/pgsql/internal/pgsql/poll.go new file mode 100644 index 0000000000..2a04c10d1f --- /dev/null +++ b/products/pgsql/internal/pgsql/poll.go @@ -0,0 +1,54 @@ +package pgsql + +import ( + "fmt" + "io" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// stopPgsqlIns stops the instance and narrates progress to out (the caller passes +// ctx.ProgressWriter(): stdout in table mode, stderr in json/yaml). Returns the +// stop error so callers can decide whether to record a structured result. +// Mirrors products/mysql/internal/mysql/poll.go stopUdbIns. +func stopPgsqlIns(ctx *cli.Context, req *upgsql.StopUPgSQLInstanceRequest, async bool, out io.Writer) error { + client := newUPgSQLClient(ctx) + _, err := client.StopUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + return err + } + text := fmt.Sprintf("pgsql[%s] is stopping", *req.InstanceID) + if async { + fmt.Fprintln(out, text) + } else { + ctx.PollerTo(out, describePgsqlByID(ctx)).Spoll(*req.InstanceID, text, []string{PGSQL_STOPPED, PGSQL_SHUTDOWN_FAILED}) + } + return nil +} + +// describePgsqlByID returns the poller's describe func, closing over ctx so it +// can build an authed upgsql client. Mirrors products/mysql/internal/mysql/poll.go +// describeUdbByID, but uses GetUPgSQLInstance (single-instance describe). +func describePgsqlByID(ctx *cli.Context) func(instanceID string, commonBase *request.CommonBase) (interface{}, error) { + return func(instanceID string, commonBase *request.CommonBase) (interface{}, error) { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLInstanceRequest() + if commonBase != nil { + req.CommonBase = *commonBase + } + req.InstanceID = sdk.String(instanceID) + resp, err := client.GetUPgSQLInstance(req) + if err != nil { + return nil, err + } + if resp.DataSet.InstanceID == "" { + return nil, fmt.Errorf("pgsql[%s] may not exist", instanceID) + } + return &resp.DataSet, nil + } +} diff --git a/products/pgsql/internal/pgsql/price.go b/products/pgsql/internal/pgsql/price.go new file mode 100644 index 0000000000..c78d984e59 --- /dev/null +++ b/products/pgsql/internal/pgsql/price.go @@ -0,0 +1,63 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newPrice ucloud pgsql db price +func newPrice(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLInstancePriceRequest() + cmd := &cobra.Command{ + Use: "price", + Short: "Get the price of creating UPgSQL instances", + Long: "Get the price of creating UPgSQL instances", + Run: func(c *cobra.Command, args []string) { + if *req.ChargeType == "Dynamic" { + req.Quantity = sdk.Int(0) + } + resp, err := client.GetUPgSQLInstancePrice(req) + if err != nil { + ctx.HandleError(err) + return + } + rows := []PgsqlPriceRow{} + for _, p := range resp.PriceSet { + rows = append(rows, PgsqlPriceRow{ + ChargeType: p.ChargeType, + Price: p.Price, + OriginalPrice: p.OriginalPrice, + }) + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.MachineType = flags.String("machine-type", "", "Required. Machine type ID, e.g. o.pgsql2m.medium. See 'ucloud pgsql db list-machine-type'") + req.DiskSpace = flags.Int("disk-size-gb", 0, "Required. Disk space (GiB)") + req.InstanceMode = flags.String("mode", "Normal", "Required. Normal / HA") + req.ChargeType = flags.String("charge-type", "Month", "Optional. Year / Month / Dynamic") + req.Quantity = flags.Int("quantity", 1, "Optional. Purchase duration. Month: 1-9, 0=until end of month; Dynamic: ignored; Year: years") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Dynamic") + command.SetFlagValues(cmd, "mode", "Normal", "HA") + command.SetCompletion(cmd, "machine-type", func() []string { + return listMachineTypeIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("machine-type") + cmd.MarkFlagRequired("disk-size-gb") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/reset_password.go b/products/pgsql/internal/pgsql/reset_password.go new file mode 100644 index 0000000000..890e5ac01a --- /dev/null +++ b/products/pgsql/internal/pgsql/reset_password.go @@ -0,0 +1,54 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newResetPassword ucloud pgsql db reset-password +func newResetPassword(ctx *cli.Context) *cobra.Command { + var idNames []string + client := newUPgSQLClient(ctx) + req := client.NewUpdateUPgSQLPasswordRequest() + cmd := &cobra.Command{ + Use: "reset-password", + Short: "Reset the admin password of UPgSQL instances", + Long: "Reset the admin password of UPgSQL instances", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + _, err := client.UpdateUPgSQLPassword(req) + if err != nil { + ctx.HandleError(err) + continue + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "reset-password", Status: "PasswordReset"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of UPgSQL instances to reset password") + req.Password = flags.String("password", "", "Required. New password") + req.Name = flags.String("name", "", "Optional. Database user name, default root") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("password") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/restart.go b/products/pgsql/internal/pgsql/restart.go new file mode 100644 index 0000000000..071b50eede --- /dev/null +++ b/products/pgsql/internal/pgsql/restart.go @@ -0,0 +1,66 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newRestart ucloud pgsql db restart +func newRestart(ctx *cli.Context) *cobra.Command { + var async bool + var idNames []string + client := newUPgSQLClient(ctx) + req := client.NewRestartUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart UPgSQL instances by instance-id", + Long: "Restart UPgSQL instances by instance-id", + Run: func(c *cobra.Command, args []string) { + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + _, err := client.RestartUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + if async { + fmt.Fprintf(w, "pgsql[%s] is restarting\n", idname) + } else { + text := fmt.Sprintf("pgsql[%s] is restarting", idname) + ctx.PollerTo(w, describePgsqlByID(ctx)).Spoll(id, text, []string{PGSQL_RUNNING, PGSQL_START_FAILED, PGSQL_SHUTDOWN_FAILED}) + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "restart", Status: "Restarting"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of UPgSQL instances to restart") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + req.ForceToRestart = flags.Bool("force", false, "Optional. Restart UPgSQL instances by force or not") + req.RestartHost = flags.Bool("restart-host", false, "Optional. Restart the host together or not") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish.") + + cmd.MarkFlagRequired("instance-id") + + command.SetFlagValues(cmd, "force", "true", "false") + command.SetFlagValues(cmd, "restart-host", "true", "false") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + return cmd +} diff --git a/products/pgsql/internal/pgsql/rows.go b/products/pgsql/internal/pgsql/rows.go new file mode 100644 index 0000000000..e66e514df2 --- /dev/null +++ b/products/pgsql/internal/pgsql/rows.go @@ -0,0 +1,120 @@ +package pgsql + +// PgsqlInstanceRow is the table row for `pgsql db list`. +type PgsqlInstanceRow struct { + Name string + InstanceID string + Zone string + State string + IP string + VPC string + Subnet string + InstanceMode string + DBVersion string + Port int + DiskSpace int + Memory int +} + +// PgsqlMachineTypeRow is the table row for `pgsql db list-machine-type`. +type PgsqlMachineTypeRow struct { + ID string + Description string + Cpu int + Memory int + Os string +} + +// PgsqlVersionRow is the table row for `pgsql db list-version`. +type PgsqlVersionRow struct { + DBVersion string + Available string +} + +// PgsqlConfRow is the table row for `pgsql conf list`. +type PgsqlConfRow struct { + GroupID int + GroupName string + DBVersion string + Description string + Modifiable bool +} + +// PgsqlConfParamRow is the table row for `pgsql conf describe` parameter list. +type PgsqlConfParamRow struct { + Key string + Value string + Modifiable bool +} + +// PgsqlBackupRow is the table row for `pgsql backup list`. +type PgsqlBackupRow struct { + BackupID string + BackupName string + InstanceID string + State string + BackupType string + BackupSize string + BackupStartTime string + BackupEndTime string +} + +// PgsqlBackupURLRow is the table row for `pgsql backup download`. +type PgsqlBackupURLRow struct { + BackupPath string + InnerBackupPath string +} + +// PgsqlBackupStrategyRow is the table row for `pgsql backup strategy`. +type PgsqlBackupStrategyRow struct { + BackupMethod string + BackupTimeRange string + BackupWeek string +} + +// PgsqlLogRow is the table row for `pgsql log list`. +type PgsqlLogRow struct { + Name string + Size string + BeginTime string + EndTime string +} + +// PgsqlPriceRow is the table row for `pgsql db price` / `upgrade-price`. +type PgsqlPriceRow struct { + ChargeType string + Price float64 + OriginalPrice float64 +} + +// SupabaseInstanceRow is the table row for `pgsql supabase list`. +type SupabaseInstanceRow struct { + USupabaseName string + InstanceID string + UPgSQLID string + Zone string + IntranetAddress string + Port int + State string +} + +// SupabaseStorageConfigRow is the table row for `pgsql supabase get-storage-config`. +type SupabaseStorageConfigRow struct { + Key string + Value string + Description string + Required bool +} + +// SupabaseAPIKeyRow is the table row for `pgsql supabase get-api-key`. +type SupabaseAPIKeyRow struct { + ServiceKey string + AnonKey string +} + +// SupabaseChargeRow is the table row for `pgsql supabase external-price` / +// `bandwidth-upgrade-price`. +type SupabaseChargeRow struct { + ChargeType string + Price int +} diff --git a/products/pgsql/internal/pgsql/start.go b/products/pgsql/internal/pgsql/start.go new file mode 100644 index 0000000000..85568db2c4 --- /dev/null +++ b/products/pgsql/internal/pgsql/start.go @@ -0,0 +1,61 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newStart ucloud pgsql db start +func newStart(ctx *cli.Context) *cobra.Command { + var async bool + var idNames []string + client := newUPgSQLClient(ctx) + req := client.NewStartUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "start", + Short: "Start UPgSQL instances by instance-id", + Long: "Start UPgSQL instances by instance-id", + Run: func(c *cobra.Command, args []string) { + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + _, err := client.StartUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + if async { + fmt.Fprintf(w, "pgsql[%s] is starting\n", idname) + } else { + text := fmt.Sprintf("pgsql[%s] is starting", idname) + ctx.PollerTo(w, describePgsqlByID(ctx)).Spoll(id, text, []string{PGSQL_RUNNING, PGSQL_START_FAILED}) + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "start", Status: "Starting"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of UPgSQL instances to start") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish.") + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + return cmd +} diff --git a/products/pgsql/internal/pgsql/status.go b/products/pgsql/internal/pgsql/status.go new file mode 100644 index 0000000000..527efe3693 --- /dev/null +++ b/products/pgsql/internal/pgsql/status.go @@ -0,0 +1,47 @@ +package pgsql + +// UPgSQL-domain state constants. +// +// These follow the LIVE API State enum (as observed from ListUPgSQLInstance / +// GetUPgSQLInstance responses): Initing / InitFailed / Starting / Running / +// Stopping / Stopped / Deleted / Upgrading / Promoting / Recovering / +// RecoverFailed / StartFailed / ShutdownFailed / Deleting / DeleteFailed. +// +// NOTE: the published GetUPgSQLInstance doc lists "Shutoff"/"Shutdown"/"Fail" +// but the live API does NOT return those — a stopped instance reports State= +// "Stopped" (not "Shutoff"). Using the doc values caused the stop poller to +// never match its target and spin until the 10m timeout. Trust the live enum. +const ( + PGSQL_RUNNING = "Running" + PGSQL_STOPPING = "Stopping" + PGSQL_STOPPED = "Stopped" + PGSQL_INITING = "Initing" + PGSQL_INIT_FAILED = "InitFailed" + PGSQL_STARTING = "Starting" + PGSQL_START_FAILED = "StartFailed" + PGSQL_SHUTDOWN_FAILED = "ShutdownFailed" + PGSQL_DELETING = "Deleting" + PGSQL_DELETED = "Deleted" + PGSQL_DELETE_FAILED = "DeleteFailed" + PGSQL_UPGRADING = "Upgrading" + PGSQL_PROMOTING = "Promoting" + PGSQL_RECOVERING = "Recovering" + PGSQL_RECOVER_FAILED = "RecoverFailed" +) + +// Backup state constants, from ListUPgSQLBackup.UPgSQLBackup.State enum values +// (Backuping / Success / Failed / Expired). Display-only; backup ops are not polled. +const ( + PGSQL_BACKUP_SUCCESS = "Success" + PGSQL_BACKUP_FAILED = "Failed" +) + +// USupabase state constants (live-observed from ListUSupabaseInstance / +// DescribeUSupabase responses). Used as poll targets for start/stop/restart. +// As with the pgsql enum, the published doc is untrusted; these are confirmed +// live (Running confirmed; Stopped pending a live stop verification). +const ( + SUPABASE_RUNNING = "Running" + SUPABASE_STOPPED = "Stopped" + SUPABASE_FAIL = "Fail" +) diff --git a/products/pgsql/internal/pgsql/stop.go b/products/pgsql/internal/pgsql/stop.go new file mode 100644 index 0000000000..ad2feff783 --- /dev/null +++ b/products/pgsql/internal/pgsql/stop.go @@ -0,0 +1,58 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newStop ucloud pgsql db stop +func newStop(ctx *cli.Context) *cobra.Command { + var idNames []string + var async bool + client := newUPgSQLClient(ctx) + req := client.NewStopUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "stop", + Short: "Stop UPgSQL instances by instance-id", + Long: "Stop UPgSQL instances by instance-id", + Run: func(c *cobra.Command, args []string) { + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + if err := stopPgsqlIns(ctx, req, async, w); err != nil { + ctx.HandleError(err) + continue + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "stop", Status: "Stopping"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of UPgSQL instances to stop") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + req.ForceToStop = flags.Bool("force", false, "Optional. Stop UPgSQL instances by force or not") + req.StopHost = flags.Bool("stop-host", false, "Optional. Stop the host together or not") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish.") + + cmd.MarkFlagRequired("instance-id") + + command.SetFlagValues(cmd, "force", "true", "false") + command.SetFlagValues(cmd, "stop-host", "true", "false") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/stop_creating_readonly.go b/products/pgsql/internal/pgsql/stop_creating_readonly.go new file mode 100644 index 0000000000..c49b67dd53 --- /dev/null +++ b/products/pgsql/internal/pgsql/stop_creating_readonly.go @@ -0,0 +1,51 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newStopCreatingReadonly ucloud pgsql db stop-creating-readonly +func newStopCreatingReadonly(ctx *cli.Context) *cobra.Command { + var idNames []string + client := newUPgSQLClient(ctx) + req := client.NewStopUPgSQLCreatingReadonlyRequest() + cmd := &cobra.Command{ + Use: "stop-creating-readonly", + Short: "Stop readonly replicas that are still being created", + Long: "Stop readonly replicas of a UPgSQL instance that are still being created", + Run: func(c *cobra.Command, args []string) { + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + _, err := client.StopUPgSQLCreatingReadonly(req) + if err != nil { + ctx.HandleError(err) + continue + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "stop-creating-readonly", Status: "Stopped"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of the readonly replicas to stop creating") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase.go b/products/pgsql/internal/pgsql/supabase.go new file mode 100644 index 0000000000..0974adeb5e --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase.go @@ -0,0 +1,39 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newPgsqlSupabase ucloud pgsql supabase +// +// USupabase is a pgsql-attached enhancement product (a community Supabase stack +// deployed onto a UPgSQL host). Its gateway actions are not in ucloud-sdk-go, so +// they are invoked generically (see supabase_client.go). The MemoryDB (AI memory) +// variant is the same backend toggled by --memory-db; create has its own action. +func newPgsqlSupabase(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "supabase", + Short: "Manage USupabase instances (and the MemoryDB AI-memory variant)", + Long: "Manage USupabase instances (and the MemoryDB AI-memory variant)", + } + cmd.AddCommand(newSupabaseList(ctx)) + cmd.AddCommand(newSupabaseDescribe(ctx)) + cmd.AddCommand(newSupabaseCreate(ctx)) + cmd.AddCommand(newSupabaseCreateMemoryDB(ctx)) + cmd.AddCommand(newSupabaseDelete(ctx)) + cmd.AddCommand(newSupabaseStart(ctx)) + cmd.AddCommand(newSupabaseStop(ctx)) + cmd.AddCommand(newSupabaseRestart(ctx)) + cmd.AddCommand(newSupabaseResetPassword(ctx)) + cmd.AddCommand(newSupabaseGetAPIKey(ctx)) + cmd.AddCommand(newSupabaseGetStorageConfig(ctx)) + cmd.AddCommand(newSupabaseSetStorageConfig(ctx)) + cmd.AddCommand(newSupabaseEnableExternal(ctx)) + cmd.AddCommand(newSupabaseDisableExternal(ctx)) + cmd.AddCommand(newSupabaseModifyExternal(ctx)) + cmd.AddCommand(newSupabaseExternalPrice(ctx)) + cmd.AddCommand(newSupabaseBandwidthUpgradePrice(ctx)) + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_bandwidth_upgrade_price.go b/products/pgsql/internal/pgsql/supabase_bandwidth_upgrade_price.go new file mode 100644 index 0000000000..29be42dd2b --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_bandwidth_upgrade_price.go @@ -0,0 +1,51 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseBandwidthUpgradePrice ucloud pgsql supabase bandwidth-upgrade-price +func newSupabaseBandwidthUpgradePrice(ctx *cli.Context) *cobra.Command { + var instanceID string + var bandwidth int + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "bandwidth-upgrade-price", + Short: "Get the price of upgrading external-access bandwidth of a USupabase instance", + Long: "Get the price of upgrading external-access bandwidth of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + params["Bandwidth"] = bandwidth + payload, err := invokeSupabase(ctx, "DescribeUSupabaseBandwithUpgradePrice", params) + if err != nil { + ctx.HandleError(err) + return + } + rows := []SupabaseChargeRow{} + if ds, ok := payload["DataSet"].([]interface{}); ok { + for _, item := range ds { + m, _ := item.(map[string]interface{}) + rows = append(rows, SupabaseChargeRow{ + ChargeType: getString(m, "ChargeType"), + Price: getInt(m, "Price"), + }) + } + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.IntVar(&bandwidth, "bandwidth", 0, "Required. Target bandwidth (Mbps)") + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("bandwidth") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_client.go b/products/pgsql/internal/pgsql/supabase_client.go new file mode 100644 index 0000000000..2a876627e9 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_client.go @@ -0,0 +1,58 @@ +package pgsql + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/services/uaccount" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newUSupabaseClient returns an authed generic SDK client whose requests are +// JSON-encoded, reusing the platform credential/handlers (so AK/SK and OAuth +// profiles both sign correctly, and project-id normalization / OAuth retry +// apply uniformly). ucloud-sdk-go has no typed USupabase methods, so supabase +// actions are invoked generically: a simple map payload signed and POSTed as a +// JSON body. JSON (not form) is mandatory — the USupabase gateway does +// json.Unmarshal on the request and fails on string-into-int fields (same +// 214001 bug as UPgSQL). +func newUSupabaseClient(ctx *cli.Context) *uaccount.UAccountClient { + client := cli.NewServiceClient(ctx, uaccount.NewClient) + _ = client.AddRequestHandler(func(c *sdk.Client, req request.Common) (request.Common, error) { + req.SetEncoder(request.NewJSONEncoder(c.GetConfig(), c.GetCredential())) + return req, nil + }) + return client +} + +// invokeSupabase calls a USupabase action with a simple map payload and returns +// the response payload map. Region/Zone/ProjectId/IsMemoryDB and business fields +// are all part of params; the caller is responsible for putting them in. The +// SDK signs the map (cred.Apply adds PublicKey + Signature) and the JSONEncoder +// marshals it with native types. A non-zero RetCode is returned as an error by +// GenericInvoke (RetCodePatcher), so callers forward it to ctx.HandleError. +func invokeSupabase(ctx *cli.Context, action string, params map[string]interface{}) (map[string]interface{}, error) { + client := newUSupabaseClient(ctx) + req := client.NewGenericRequest() + payload := make(map[string]interface{}, len(params)+1) + payload["Action"] = action + for k, v := range params { + payload[k] = v + } + if err := req.SetPayload(payload); err != nil { + return nil, fmt.Errorf("set payload: %w", err) + } + resp, err := client.GenericInvoke(req) + if err != nil { + return nil, err + } + return resp.GetPayload(), nil +} + +// supabaseState is a typed view over a generic response's State field, used to +// avoid repeated map[string]interface{} casts in command code. +type supabaseState struct { + State string +} diff --git a/products/pgsql/internal/pgsql/supabase_create.go b/products/pgsql/internal/pgsql/supabase_create.go new file mode 100644 index 0000000000..06b9bc900e --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_create.go @@ -0,0 +1,126 @@ +package pgsql + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// supabaseCreateFlags holds the shared business flags for CreateUSupabase and +// CreateUMemoryDB (the two requests have identical business fields). +type supabaseCreateFlags struct { + common *supabaseCommon + instanceName, dashboardName, dashboardPassword, upgsqlUserName, dbVersion string + upgsqlPassword, machineType, subnetID, vpcID, instanceMode, chargeType string + usupabasePort, paramGroupID, upgsqlPort, diskSpace, quantity int + labels []string +} + +func bindSupabaseCreate(cmd *cobra.Command, ctx *cli.Context) *supabaseCreateFlags { + f := &supabaseCreateFlags{} + f.common = bindSupabaseCommon(cmd, ctx) + flags := cmd.Flags() + flags.StringVar(&f.instanceName, "name", "", "Required. Supabase instance name") + flags.StringVar(&f.dashboardName, "dashboard-name", "", "Required. Dashboard user name") + flags.StringVar(&f.dashboardPassword, "dashboard-password", "", "Required. Dashboard password") + flags.IntVar(&f.usupabasePort, "supabase-port", 8000, "Optional. Supabase service port, default 8000") + flags.StringVar(&f.upgsqlUserName, "pgsql-user", "", "Required. UPgSQL user name") + flags.StringVar(&f.dbVersion, "db-version", "", "Required. UPgSQL version, e.g. postgresql-13.4") + flags.IntVar(&f.paramGroupID, "param-group-id", 0, "Required. UPgSQL param group ID") + flags.StringVar(&f.upgsqlPassword, "pgsql-password", "", "Required. UPgSQL password") + flags.IntVar(&f.upgsqlPort, "pgsql-port", 5432, "Optional. UPgSQL port, default 5432") + flags.IntVar(&f.diskSpace, "disk-size-gb", 0, "Required. Disk space (GiB)") + flags.StringVar(&f.machineType, "machine-type", "", "Required. Machine type, e.g. o.pgsql2m.medium. See 'ucloud pgsql db list-machine-type'") + flags.StringVar(&f.subnetID, "subnet-id", "", "Required. Subnet ID") + flags.StringVar(&f.vpcID, "vpc-id", "", "Required. VPC ID") + flags.StringVar(&f.instanceMode, "mode", "Normal", "Optional. Normal / HA") + flags.StringVar(&f.chargeType, "charge-type", "Month", "Optional. Year / Month / Dynamic") + flags.IntVar(&f.quantity, "quantity", 1, "Optional. Purchase duration") + flags.StringSliceVar(&f.labels, "label", nil, "Optional. Resource label key=value (repeatable)") + command.SetFlagValues(cmd, "db-version", pgsqlVersionList...) + command.SetFlagValues(cmd, "mode", "Normal", "HA") + command.SetFlagValues(cmd, "charge-type", "Year", "Month", "Dynamic") + return f +} + +func (f *supabaseCreateFlags) params() map[string]interface{} { + p := f.common.params() + p["InstanceName"] = f.instanceName + p["DashboardName"] = f.dashboardName + p["DashboardPassword"] = f.dashboardPassword + p["USupabasePort"] = f.usupabasePort + p["UPgSQLUserName"] = f.upgsqlUserName + p["DBVersion"] = f.dbVersion + p["ParamGroupID"] = f.paramGroupID + p["UPgSQLPassword"] = f.upgsqlPassword + p["UPgSQLPort"] = f.upgsqlPort + p["DiskSpace"] = f.diskSpace + p["MachineType"] = f.machineType + p["SubnetID"] = f.subnetID + p["VPCID"] = f.vpcID + p["InstanceMode"] = f.instanceMode + p["ChargeType"] = f.chargeType + p["Quantity"] = f.quantity + labels := []map[string]interface{}{} + for _, l := range f.labels { + parts := strings.SplitN(l, "=", 2) + if len(parts) == 2 { + labels = append(labels, map[string]interface{}{"Key": parts[0], "Value": parts[1]}) + } + } + p["Labels"] = labels + return p +} + +// runSupabaseCreate is shared by create and create-memory-db: invoke the action, +// poll the returned InstanceID to Running, emit the result. +func runSupabaseCreate(ctx *cli.Context, action string, f *supabaseCreateFlags, async bool) { + params := f.params() + payload, err := invokeSupabase(ctx, action, params) + if err != nil { + ctx.HandleError(err) + return + } + instanceID := getString(payload, "InstanceID") + if instanceID == "" { + ctx.HandleError(fmt.Errorf("empty InstanceID in response")) + return + } + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "supabase[%s] is initializing\n", instanceID) + } else { + text := fmt.Sprintf("supabase[%s] is initializing", instanceID) + ctx.PollerTo(w, describeSupabaseByID(ctx, f.common.region, f.common.zone, f.common.projectID, f.common.memoryDB)). + Spoll(instanceID, text, []string{SUPABASE_RUNNING, SUPABASE_FAIL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "create", Status: "Initializing"}) +} + +// newSupabaseCreate ucloud pgsql supabase create +func newSupabaseCreate(ctx *cli.Context) *cobra.Command { + var async bool + var f *supabaseCreateFlags + cmd := &cobra.Command{ + Use: "create", + Short: "Create a USupabase instance", + Long: "Create a USupabase instance (deploys a Supabase stack onto a UPgSQL host)", + Run: func(c *cobra.Command, args []string) { + runSupabaseCreate(ctx, "CreateUSupabase", f, async) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + f = bindSupabaseCreate(cmd, ctx) + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + + for _, req := range []string{"name", "dashboard-name", "dashboard-password", "pgsql-user", "db-version", "param-group-id", "pgsql-password", "disk-size-gb", "machine-type", "subnet-id", "vpc-id"} { + cmd.MarkFlagRequired(req) + } + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_create_memory_db.go b/products/pgsql/internal/pgsql/supabase_create_memory_db.go new file mode 100644 index 0000000000..1c693782b1 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_create_memory_db.go @@ -0,0 +1,35 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseCreateMemoryDB ucloud pgsql supabase create-memory-db +// +// The MemoryDB (AI memory) variant uses the same business fields as create but +// the dedicated CreateUMemoryDB action (CreateUSupabaseRequest has no IsMemoryDB +// field, so the two are distinguished by action name, not a flag). +func newSupabaseCreateMemoryDB(ctx *cli.Context) *cobra.Command { + var async bool + var f *supabaseCreateFlags + cmd := &cobra.Command{ + Use: "create-memory-db", + Short: "Create a UMemoryDB (AI memory) instance", + Long: "Create a UMemoryDB (AI memory) instance — the Supabase-based AI memory variant", + Run: func(c *cobra.Command, args []string) { + runSupabaseCreate(ctx, "CreateUMemoryDB", f, async) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + f = bindSupabaseCreate(cmd, ctx) + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + + for _, req := range []string{"name", "dashboard-name", "dashboard-password", "pgsql-user", "db-version", "param-group-id", "pgsql-password", "disk-size-gb", "machine-type", "subnet-id", "vpc-id"} { + cmd.MarkFlagRequired(req) + } + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_delete.go b/products/pgsql/internal/pgsql/supabase_delete.go new file mode 100644 index 0000000000..cac85e3322 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_delete.go @@ -0,0 +1,49 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseDelete ucloud pgsql supabase delete +func newSupabaseDelete(ctx *cli.Context) *cobra.Command { + var instanceID string + var yes bool + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "delete", + Short: "Delete a USupabase instance", + Long: "Delete a USupabase instance", + Run: func(c *cobra.Command, args []string) { + ok, err := ctx.Confirm(yes, fmt.Sprintf("Are you sure you want to delete supabase[%s]?", instanceID)) + if err != nil { + ctx.HandleError(err) + return + } + if !ok { + return + } + params := common.params() + params["InstanceID"] = instanceID + if _, err := invokeSupabase(ctx, "DeleteUSupabase", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] deleted\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "delete", Status: "Deleted"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.BoolVarP(&yes, "yes", "y", false, "Optional. Do not prompt for confirmation") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_describe.go b/products/pgsql/internal/pgsql/supabase_describe.go new file mode 100644 index 0000000000..85cc74cc0a --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_describe.go @@ -0,0 +1,62 @@ +package pgsql + +import ( + "fmt" + "strconv" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseDescribe ucloud pgsql supabase describe +func newSupabaseDescribe(ctx *cli.Context) *cobra.Command { + var instanceID string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "describe", + Short: "Display details of a USupabase instance", + Long: "Display details of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + payload, err := invokeSupabase(ctx, "DescribeUSupabase", params) + if err != nil { + ctx.HandleError(err) + return + } + ds, ok := payload["DataSet"].(map[string]interface{}) + if !ok { + ctx.HandleError(fmt.Errorf("pgsql supabase[%s] may not exist", instanceID)) + return + } + attrs := []cli.DescribeRow{ + {Attribute: "InstanceID", Content: getString(ds, "InstanceID")}, + {Attribute: "USupabaseName", Content: getString(ds, "USupabaseName")}, + {Attribute: "State", Content: getString(ds, "State")}, + {Attribute: "Zone", Content: getString(ds, "Zone")}, + {Attribute: "UPgSQLID", Content: getString(ds, "UPgSQLID")}, + {Attribute: "VPCID", Content: getString(ds, "VPCID")}, + {Attribute: "SubnetID", Content: getString(ds, "SubnetID")}, + {Attribute: "IntranetAddress", Content: getString(ds, "IntranetAddress")}, + {Attribute: "Port", Content: strconv.Itoa(getInt(ds, "Port"))}, + {Attribute: "ExternalNetworkStatus", Content: getString(ds, "ExternalNetworkStatus")}, + {Attribute: "ExternalNetworkAddress", Content: getString(ds, "ExternalNetworkAddress")}, + {Attribute: "ExternalNetworkPort", Content: strconv.Itoa(getInt(ds, "ExternalNetworkPort"))}, + {Attribute: "Bandwidth", Content: strconv.Itoa(getInt(ds, "Bandwidth"))}, + {Attribute: "WhiteList", Content: getString(ds, "WhiteList")}, + } + fmt.Fprintln(ctx.ProgressWriter(), "Attributes:") + ctx.PrintList(attrs) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_disable_external.go b/products/pgsql/internal/pgsql/supabase_disable_external.go new file mode 100644 index 0000000000..3e9426e45d --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_disable_external.go @@ -0,0 +1,39 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseDisableExternal ucloud pgsql supabase disable-external-access +func newSupabaseDisableExternal(ctx *cli.Context) *cobra.Command { + var instanceID string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "disable-external-access", + Short: "Disable external access for a USupabase instance", + Long: "Disable external network access for a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + if _, err := invokeSupabase(ctx, "DisableUSupabaseExternalAccess", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] external access disabled\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "disable-external-access", Status: "Disabled"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_enable_external.go b/products/pgsql/internal/pgsql/supabase_enable_external.go new file mode 100644 index 0000000000..2915d39206 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_enable_external.go @@ -0,0 +1,43 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseEnableExternal ucloud pgsql supabase enable-external-access +func newSupabaseEnableExternal(ctx *cli.Context) *cobra.Command { + var instanceID string + var bandwidth int + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "enable-external-access", + Short: "Enable external access for a USupabase instance", + Long: "Enable external network access for a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + params["Bandwidth"] = bandwidth + if _, err := invokeSupabase(ctx, "EnableUSupabaseExternalAccess", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] external access enabled\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "enable-external-access", Status: "Enabled"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.IntVar(&bandwidth, "bandwidth", 0, "Required. Bandwidth (Mbps)") + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("bandwidth") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_external_price.go b/products/pgsql/internal/pgsql/supabase_external_price.go new file mode 100644 index 0000000000..8131b87bb2 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_external_price.go @@ -0,0 +1,51 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseExternalPrice ucloud pgsql supabase external-price +func newSupabaseExternalPrice(ctx *cli.Context) *cobra.Command { + var instanceID string + var bandwidth int + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "external-price", + Short: "Get the price of enabling external access for a USupabase instance", + Long: "Get the price of enabling external access for a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + params["BandWidth"] = bandwidth + payload, err := invokeSupabase(ctx, "DescribeUSupabaseExternalPrice", params) + if err != nil { + ctx.HandleError(err) + return + } + rows := []SupabaseChargeRow{} + if ds, ok := payload["DataSet"].([]interface{}); ok { + for _, item := range ds { + m, _ := item.(map[string]interface{}) + rows = append(rows, SupabaseChargeRow{ + ChargeType: getString(m, "ChargeType"), + Price: getInt(m, "Price"), + }) + } + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.IntVar(&bandwidth, "bandwidth", 0, "Required. Bandwidth (Mbps)") + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("bandwidth") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_get_api_key.go b/products/pgsql/internal/pgsql/supabase_get_api_key.go new file mode 100644 index 0000000000..2f1347bbbf --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_get_api_key.go @@ -0,0 +1,41 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseGetAPIKey ucloud pgsql supabase get-api-key +func newSupabaseGetAPIKey(ctx *cli.Context) *cobra.Command { + var instanceID string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "get-api-key", + Short: "Display the API keys (service key + anon key) of a USupabase instance", + Long: "Display the API keys (service key + anon key) of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + payload, err := invokeSupabase(ctx, "GetUSupabaseAPIKey", params) + if err != nil { + ctx.HandleError(err) + return + } + key, _ := payload["Key"].(map[string]interface{}) + ctx.PrintList([]SupabaseAPIKeyRow{{ + ServiceKey: getString(key, "ServiceKey"), + AnonKey: getString(key, "AnonKey"), + }}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_get_storage_config.go b/products/pgsql/internal/pgsql/supabase_get_storage_config.go new file mode 100644 index 0000000000..566549b7dc --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_get_storage_config.go @@ -0,0 +1,49 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseGetStorageConfig ucloud pgsql supabase get-storage-config +func newSupabaseGetStorageConfig(ctx *cli.Context) *cobra.Command { + var instanceID string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "get-storage-config", + Short: "Display the storage configuration of a USupabase instance", + Long: "Display the storage configuration of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + payload, err := invokeSupabase(ctx, "GetUSupabaseStorageConfig", params) + if err != nil { + ctx.HandleError(err) + return + } + rows := []SupabaseStorageConfigRow{} + if ds, ok := payload["DataSet"].([]interface{}); ok { + for _, item := range ds { + m, _ := item.(map[string]interface{}) + rows = append(rows, SupabaseStorageConfigRow{ + Key: getString(m, "Key"), + Value: getString(m, "Value"), + Description: getString(m, "Description"), + Required: getBool(m, "Required"), + }) + } + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_list.go b/products/pgsql/internal/pgsql/supabase_list.go new file mode 100644 index 0000000000..9a77036b1b --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_list.go @@ -0,0 +1,133 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// supabaseCommon holds the region/zone/project-id/memory-db flags shared by +// every supabase command. Bound via bindSupabaseCommon so flag order stays +// consistent across verbs (the completion golden depends on it). +type supabaseCommon struct { + region string + zone string + projectID string + memoryDB bool +} + +// bindSupabaseCommon registers --region/--zone/--project-id/--memory-db with +// ctx defaults + completion. ProjectId is ALWAYS bound: USupabase IAM checks +// require the ProjectId context, so every action carries it. +func bindSupabaseCommon(cmd *cobra.Command, ctx *cli.Context) *supabaseCommon { + c := &supabaseCommon{} + flags := cmd.Flags() + flags.StringVar(&c.region, "region", ctx.DefaultRegion(), "Optional. Override default region, see 'ucloud region'") + flags.StringVar(&c.zone, "zone", ctx.DefaultZone(), "Optional. Override default zone, see 'ucloud region'") + flags.StringVar(&c.projectID, "project-id", ctx.DefaultProjectID(), "Optional. Override default project-id, see 'ucloud project list'") + flags.BoolVar(&c.memoryDB, "memory-db", false, "Optional. Operate on the MemoryDB (AI memory) variant") + command.SetCompletion(cmd, "region", func() []string { return ctx.RegionList() }) + command.SetCompletion(cmd, "zone", func() []string { return ctx.ZoneList(c.region) }) + command.SetCompletion(cmd, "project-id", func() []string { return ctx.ProjectList() }) + return c +} + +// params builds the map payload common to every supabase action: Region/Zone/ +// ProjectId/IsMemoryDB. ProjectId is mandatory (IAM context). Business fields +// are added by the caller. +func (c *supabaseCommon) params() map[string]interface{} { + p := map[string]interface{}{ + "Region": c.region, + "Zone": c.zone, + "ProjectId": c.projectID, + } + if c.memoryDB { + p["IsMemoryDB"] = true + } + return p +} + +// newSupabaseList ucloud pgsql supabase list +func newSupabaseList(ctx *cli.Context) *cobra.Command { + var instanceID string + var limit, offset int + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "list", + Short: "List USupabase instances", + Long: "List USupabase instances (or MemoryDB instances with --memory-db)", + Run: func(c *cobra.Command, args []string) { + params := common.params() + if instanceID != "" { + params["InstanceID"] = instanceID + } + if limit > 0 { + params["Limit"] = limit + } + if offset > 0 { + params["Offset"] = offset + } + payload, err := invokeSupabase(ctx, "ListUSupabaseInstance", params) + if err != nil { + ctx.HandleError(err) + return + } + rows := []SupabaseInstanceRow{} + if ds, ok := payload["DataSet"].([]interface{}); ok { + for _, item := range ds { + m, ok := item.(map[string]interface{}) + if !ok { + continue + } + rows = append(rows, SupabaseInstanceRow{ + USupabaseName: getString(m, "USupabaseName"), + InstanceID: getString(m, "InstanceID"), + UPgSQLID: getString(m, "UPgSQLID"), + Zone: getString(m, "Zone"), + IntranetAddress: getString(m, "IntranetAddress"), + Port: getInt(m, "Port"), + State: getString(m, "State"), + }) + } + } + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Optional. List only the specified USupabase instance") + flags.IntVar(&limit, "limit", 0, "Optional. Max instances per page (0 = default)") + flags.IntVar(&offset, "offset", 0, "Optional. Offset") + + return cmd +} + +// getString / getInt are tiny helpers over a generic response map (JSON numbers +// unmarshal as float64). +func getString(m map[string]interface{}, key string) string { + if v, ok := m[key].(string); ok { + return v + } + return "" +} + +func getInt(m map[string]interface{}, key string) int { + switch v := m[key].(type) { + case float64: + return int(v) + case int: + return v + } + return 0 +} + +func getBool(m map[string]interface{}, key string) bool { + if v, ok := m[key].(bool); ok { + return v + } + return false +} diff --git a/products/pgsql/internal/pgsql/supabase_modify_external.go b/products/pgsql/internal/pgsql/supabase_modify_external.go new file mode 100644 index 0000000000..f9d2515def --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_modify_external.go @@ -0,0 +1,52 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseModifyExternal ucloud pgsql supabase modify-external-access +func newSupabaseModifyExternal(ctx *cli.Context) *cobra.Command { + var instanceID, whiteList string + var bandwidth, port int + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "modify-external-access", + Short: "Modify external-access settings of a USupabase instance", + Long: "Modify external-access bandwidth/port/whitelist of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + if c.Flags().Changed("bandwidth") { + params["Bandwidth"] = bandwidth + } + if c.Flags().Changed("port") { + params["Port"] = port + } + if c.Flags().Changed("white-list") { + params["WhiteList"] = whiteList + } + if _, err := invokeSupabase(ctx, "ModifyUSupabaseExternalAccessInfo", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] external access modified\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "modify-external-access", Status: "Modified"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.IntVar(&bandwidth, "bandwidth", 0, "Optional. New bandwidth (Mbps)") + flags.IntVar(&port, "port", 0, "Optional. New external port") + flags.StringVar(&whiteList, "white-list", "", "Optional. New IP whitelist") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_poll.go b/products/pgsql/internal/pgsql/supabase_poll.go new file mode 100644 index 0000000000..26df50bb4d --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_poll.go @@ -0,0 +1,36 @@ +package pgsql + +import ( + "fmt" + + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// describeSupabaseByID returns the poller's describe func: DescribeUSupabase → State. +// region/zone/projectID/memoryDB are captured from the originating command's flags so +// the poll uses the same scope (USupabase IAM requires the ProjectId context). +// Signature matches pkg/cli.Poller: func(string, *request.CommonBase) (interface{}, error). +func describeSupabaseByID(ctx *cli.Context, region, zone, projectID string, memoryDB bool) func(instanceID string, _ *request.CommonBase) (interface{}, error) { + return func(instanceID string, _ *request.CommonBase) (interface{}, error) { + params := map[string]interface{}{ + "Region": region, + "Zone": zone, + "ProjectId": projectID, + "InstanceID": instanceID, + } + if memoryDB { + params["IsMemoryDB"] = true + } + payload, err := invokeSupabase(ctx, "DescribeUSupabase", params) + if err != nil { + return nil, err + } + ds, ok := payload["DataSet"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("pgsql supabase[%s] may not exist", instanceID) + } + return &supabaseState{State: getString(ds, "State")}, nil + } +} diff --git a/products/pgsql/internal/pgsql/supabase_reset_password.go b/products/pgsql/internal/pgsql/supabase_reset_password.go new file mode 100644 index 0000000000..df4322b114 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_reset_password.go @@ -0,0 +1,42 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseResetPassword ucloud pgsql supabase reset-password +func newSupabaseResetPassword(ctx *cli.Context) *cobra.Command { + var instanceID, password string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "reset-password", + Short: "Reset the dashboard password of a USupabase instance", + Long: "Reset the dashboard password of a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + params["Password"] = password + if _, err := invokeSupabase(ctx, "ResetUSupabasePassword", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] password reset\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "reset-password", Status: "PasswordReset"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.StringVar(&password, "password", "", "Required. New dashboard password") + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("password") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_restart.go b/products/pgsql/internal/pgsql/supabase_restart.go new file mode 100644 index 0000000000..7eb9038538 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_restart.go @@ -0,0 +1,48 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseRestart ucloud pgsql supabase restart +func newSupabaseRestart(ctx *cli.Context) *cobra.Command { + var instanceID string + var async bool + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "restart", + Short: "Restart a USupabase instance", + Long: "Restart a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + if _, err := invokeSupabase(ctx, "RestartUSupabase", params); err != nil { + ctx.HandleError(err) + return + } + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "supabase[%s] is restarting\n", instanceID) + } else { + text := fmt.Sprintf("supabase[%s] is restarting", instanceID) + ctx.PollerTo(w, describeSupabaseByID(ctx, common.region, common.zone, common.projectID, common.memoryDB)). + Spoll(instanceID, text, []string{SUPABASE_RUNNING, SUPABASE_FAIL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "restart", Status: "Restarting"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_set_storage_config.go b/products/pgsql/internal/pgsql/supabase_set_storage_config.go new file mode 100644 index 0000000000..e62258a213 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_set_storage_config.go @@ -0,0 +1,56 @@ +package pgsql + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseSetStorageConfig ucloud pgsql supabase set-storage-config +func newSupabaseSetStorageConfig(ctx *cli.Context) *cobra.Command { + var instanceID string + var configs []string + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "set-storage-config", + Short: "Set storage configuration entries of a USupabase instance", + Long: "Set storage configuration entries of a USupabase instance (use --config key=value, repeatable)", + Run: func(c *cobra.Command, args []string) { + entries := []map[string]interface{}{} + for _, kv := range configs { + parts := strings.SplitN(kv, "=", 2) + if len(parts) != 2 { + ctx.HandleError(fmt.Errorf("invalid --config %q, expected key=value", kv)) + return + } + entries = append(entries, map[string]interface{}{ + "Key": parts[0], + "Value": parts[1], + }) + } + params := common.params() + params["InstanceID"] = instanceID + params["ConfigEntry"] = entries + if _, err := invokeSupabase(ctx, "SetUSupabaseStorageConfig", params); err != nil { + ctx.HandleError(err) + return + } + fmt.Fprintf(ctx.ProgressWriter(), "supabase[%s] storage config set\n", instanceID) + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "set-storage-config", Status: "Set"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.StringSliceVar(&configs, "config", nil, "Required. Storage config entry, format key=value (repeatable)") + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("config") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_start.go b/products/pgsql/internal/pgsql/supabase_start.go new file mode 100644 index 0000000000..78ed02c65f --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_start.go @@ -0,0 +1,48 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseStart ucloud pgsql supabase start +func newSupabaseStart(ctx *cli.Context) *cobra.Command { + var instanceID string + var async bool + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "start", + Short: "Start a USupabase instance", + Long: "Start a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + if _, err := invokeSupabase(ctx, "StartUSupabase", params); err != nil { + ctx.HandleError(err) + return + } + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "supabase[%s] is starting\n", instanceID) + } else { + text := fmt.Sprintf("supabase[%s] is starting", instanceID) + ctx.PollerTo(w, describeSupabaseByID(ctx, common.region, common.zone, common.projectID, common.memoryDB)). + Spoll(instanceID, text, []string{SUPABASE_RUNNING, SUPABASE_FAIL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "start", Status: "Starting"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/supabase_stop.go b/products/pgsql/internal/pgsql/supabase_stop.go new file mode 100644 index 0000000000..1ecd55c809 --- /dev/null +++ b/products/pgsql/internal/pgsql/supabase_stop.go @@ -0,0 +1,48 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// newSupabaseStop ucloud pgsql supabase stop +func newSupabaseStop(ctx *cli.Context) *cobra.Command { + var instanceID string + var async bool + var common *supabaseCommon + cmd := &cobra.Command{ + Use: "stop", + Short: "Stop a USupabase instance", + Long: "Stop a USupabase instance", + Run: func(c *cobra.Command, args []string) { + params := common.params() + params["InstanceID"] = instanceID + if _, err := invokeSupabase(ctx, "StopUSupabase", params); err != nil { + ctx.HandleError(err) + return + } + w := ctx.ProgressWriter() + if async { + fmt.Fprintf(w, "supabase[%s] is stopping\n", instanceID) + } else { + text := fmt.Sprintf("supabase[%s] is stopping", instanceID) + ctx.PollerTo(w, describeSupabaseByID(ctx, common.region, common.zone, common.projectID, common.memoryDB)). + Spoll(instanceID, text, []string{SUPABASE_STOPPED, SUPABASE_FAIL}) + } + ctx.EmitResult(cli.OpResultRow{ResourceID: instanceID, Action: "stop", Status: "Stopping"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + common = bindSupabaseCommon(cmd, ctx) + flags.StringVar(&instanceID, "instance-id", "", "Required. Resource ID of the USupabase instance") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + cmd.MarkFlagRequired("instance-id") + + return cmd +} diff --git a/products/pgsql/internal/pgsql/update_name.go b/products/pgsql/internal/pgsql/update_name.go new file mode 100644 index 0000000000..f353d6aa9c --- /dev/null +++ b/products/pgsql/internal/pgsql/update_name.go @@ -0,0 +1,45 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpdateName ucloud pgsql db update-name +func newUpdateName(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewUpdateUPgSQLAttributeRequest() + cmd := &cobra.Command{ + Use: "update-name", + Short: "Update the name of a UPgSQL instance", + Long: "Update the name of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + _, err := client.UpdateUPgSQLAttribute(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceID, Action: "update-name", Status: "Updated"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.Name = flags.String("name", "", "Required. New instance name") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("name") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/update_remark.go b/products/pgsql/internal/pgsql/update_remark.go new file mode 100644 index 0000000000..46585d0bf4 --- /dev/null +++ b/products/pgsql/internal/pgsql/update_remark.go @@ -0,0 +1,64 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-sdk-go/services/upgsql" + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpdateRemark ucloud pgsql db update-remark +func newUpdateRemark(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewUpdateUPgSQLAttributeRequest() + cmd := &cobra.Command{ + Use: "update-remark", + Short: "Update the remark of a UPgSQL instance", + Long: "Update the remark of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + // UpdateUPgSQLAttribute requires Name to be present even when only the + // remark changes — omitting it returns RetCode 230 "Params [Name] not + // available". Fetch the current name and re-send it alongside Remark. + any, err := describePgsqlByID(ctx)(*req.InstanceID, nil) + if err != nil { + ctx.HandleError(err) + return + } + ins, ok := any.(*upgsql.UDBInstance) + if !ok { + ctx.HandleError(fmt.Errorf("fetch pgsql[%s] instance", *req.InstanceID)) + return + } + req.Name = sdk.String(ins.Name) + _, err = client.UpdateUPgSQLAttribute(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.EmitResult(cli.OpResultRow{ResourceID: *req.InstanceID, Action: "update-remark", Status: "Updated"}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.Remark = flags.String("remark", "", "Required. New remark") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("remark") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/upgrade.go b/products/pgsql/internal/pgsql/upgrade.go new file mode 100644 index 0000000000..836f08ffec --- /dev/null +++ b/products/pgsql/internal/pgsql/upgrade.go @@ -0,0 +1,80 @@ +package pgsql + +import ( + "fmt" + + "github.com/spf13/cobra" + + sdk "github.com/ucloud/ucloud-sdk-go/ucloud" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpgrade ucloud pgsql db upgrade +func newUpgrade(ctx *cli.Context) *cobra.Command { + var async bool + var idNames []string + var machineType string + var diskSpace int + client := newUPgSQLClient(ctx) + req := client.NewUpgradeUPgSQLInstanceRequest() + cmd := &cobra.Command{ + Use: "upgrade", + Short: "Upgrade disk space and/or machine type of UPgSQL instances", + Long: "Upgrade disk space and/or machine type of UPgSQL instances", + Run: func(c *cobra.Command, args []string) { + if machineType == "" && diskSpace == 0 { + ctx.HandleError(fmt.Errorf("at least one of --machine-type or --disk-size-gb is required")) + return + } + w := ctx.ProgressWriter() + results := []cli.OpResultRow{} + for _, idname := range idNames { + id := ctx.PickResourceID(idname) + req.InstanceID = sdk.String(id) + if machineType != "" { + req.MachineType = sdk.String(machineType) + } + if diskSpace != 0 { + req.DiskSpace = sdk.Int(diskSpace) + } + _, err := client.UpgradeUPgSQLInstance(req) + if err != nil { + ctx.HandleError(err) + continue + } + if async { + fmt.Fprintf(w, "pgsql[%s] is upgrading\n", idname) + } else { + text := fmt.Sprintf("pgsql[%s] is upgrading", idname) + ctx.PollerTo(w, describePgsqlByID(ctx)).Spoll(id, text, []string{PGSQL_RUNNING, PGSQL_INIT_FAILED, PGSQL_START_FAILED}) + } + results = append(results, cli.OpResultRow{ResourceID: id, Action: "upgrade", Status: "Upgrading"}) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&idNames, "instance-id", nil, "Required. Resource ID of UPgSQL instances to upgrade") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + flags.StringVar(&machineType, "machine-type", "", "Optional. New machine type ID. See 'ucloud pgsql db list-machine-type'") + flags.IntVar(&diskSpace, "disk-size-gb", 0, "Optional. New disk size (GiB)") + flags.BoolVarP(&async, "async", "a", false, "Optional. Do not wait for the long-running operation to finish") + + cmd.MarkFlagRequired("instance-id") + + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + command.SetCompletion(cmd, "machine-type", func() []string { + return listMachineTypeIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + return cmd +} diff --git a/products/pgsql/internal/pgsql/upgrade_price.go b/products/pgsql/internal/pgsql/upgrade_price.go new file mode 100644 index 0000000000..daf5b38d9e --- /dev/null +++ b/products/pgsql/internal/pgsql/upgrade_price.go @@ -0,0 +1,56 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// newUpgradePrice ucloud pgsql db upgrade-price +func newUpgradePrice(ctx *cli.Context) *cobra.Command { + client := newUPgSQLClient(ctx) + req := client.NewGetUPgSQLUpgradePriceRequest() + cmd := &cobra.Command{ + Use: "upgrade-price", + Short: "Get the price of upgrading a UPgSQL instance", + Long: "Get the price of upgrading disk space and/or machine type of a UPgSQL instance", + Run: func(c *cobra.Command, args []string) { + *req.InstanceID = ctx.PickResourceID(*req.InstanceID) + resp, err := client.GetUPgSQLUpgradePrice(req) + if err != nil { + ctx.HandleError(err) + return + } + ctx.PrintList([]PgsqlPriceRow{{ + Price: resp.Price, + OriginalPrice: resp.OriginalPrice, + }}) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + req.InstanceID = flags.String("instance-id", "", "Required. Resource ID of the UPgSQL instance") + req.MachineType = flags.String("machine-type", "", "Required. New machine type ID. See 'ucloud pgsql db list-machine-type'") + req.DiskSpace = flags.Int("disk-size-gb", 0, "Required. New disk space (GiB)") + req.InstanceMode = flags.String("mode", "Normal", "Optional. Normal / HA") + ctx.BindProjectID(cmd, req) + ctx.BindRegion(cmd, req) + ctx.BindZone(cmd, req) + + command.SetFlagValues(cmd, "mode", "Normal", "HA") + command.SetCompletion(cmd, "instance-id", func() []string { + return getUPgSQLIDList(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + command.SetCompletion(cmd, "machine-type", func() []string { + return listMachineTypeIDNames(ctx, req.GetProjectId(), req.GetRegion(), req.GetZone()) + }) + + cmd.MarkFlagRequired("instance-id") + cmd.MarkFlagRequired("machine-type") + cmd.MarkFlagRequired("disk-size-gb") + + return cmd +} diff --git a/products/pgsql/product.go b/products/pgsql/product.go new file mode 100644 index 0000000000..faf8234a1d --- /dev/null +++ b/products/pgsql/product.go @@ -0,0 +1,20 @@ +package pgsql + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internalpgsql "github.com/ucloud/ucloud-cli/products/pgsql/internal/pgsql" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "pgsql", Commands: []string{"pgsql"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internalpgsql.NewCommand(ctx)} +} diff --git a/products/pgsql/product.yaml b/products/pgsql/product.yaml new file mode 100644 index 0000000000..0b3459c914 --- /dev/null +++ b/products/pgsql/product.yaml @@ -0,0 +1,6 @@ +name: pgsql +owners: + - jemy-wang +commands: + - pgsql +enabled: true diff --git a/products/pgsql/testdata/cmdtree.golden b/products/pgsql/testdata/cmdtree.golden new file mode 100644 index 0000000000..c99152d73c --- /dev/null +++ b/products/pgsql/testdata/cmdtree.golden @@ -0,0 +1,358 @@ +ucloud pgsql use=pgsql short=Manipulate UPgSQL on UCloud platform +ucloud pgsql backup use=backup short=List and manipulate backups of UPgSQL instances +ucloud pgsql backup download use=download short=Display download URLs of a UPgSQL backup + flag=backup-id short= default= required=true + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql backup list use=list short=List backups of a UPgSQL instance + flag=backup-type short= default= required= + flag=instance-id short= default= required=true + flag=limit short= default=100 required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql backup strategy use=strategy short=Display the backup strategy of a UPgSQL instance + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql backup update-strategy use=update-strategy short=Update the backup strategy of a UPgSQL instance + flag=backup-method short= default= required= + flag=backup-time-range short= default= required= + flag=backup-week short= default= required= + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql conf use=conf short=List and manipulate parameter templates of UPgSQL instances +ucloud pgsql conf create use=create short=Create a UPgSQL parameter template from a base template + flag=db-version short= default= required=true + flag=description short= default= required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=src-conf-id short= default= required=true + flag=zone short= default= required= +ucloud pgsql conf delete use=delete short=Delete a UPgSQL parameter template + flag=conf-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql conf describe use=describe short=Display details of a UPgSQL parameter template + flag=conf-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql conf download use=download short=Download a UPgSQL parameter template (base64 content) + flag=conf-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql conf list use=list short=List UPgSQL parameter templates + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql conf upload use=upload short=Create a UPgSQL parameter template by uploading a local config file + flag=conf-file short= default= required=true + flag=db-version short= default= required=true + flag=description short= default= required= + flag=name short= default= required=true + flag=param-group-type short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db use=db short=Manage UPgSQL instances +ucloud pgsql db create use=create short=Create a UPgSQL instance + flag=async short=a default=false required= + flag=disk-size-gb short= default=100 required= + flag=machine-type short= default= required=true + flag=mode short= default=Normal required= + flag=name short= default= required=true + flag=param-group-id short= default=0 required= + flag=password short= default= required=true + flag=port short= default=5432 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=subnet-id short= default= required=true + flag=version short= default= required=true + flag=vpc-id short= default= required=true + flag=zone short= default= required= +ucloud pgsql db create-readonly use=create-readonly short=Create a readonly replica for a UPgSQL instance + flag=async short=a default=false required= + flag=disk-size-gb short= default=0 required=true + flag=machine-type short= default= required=true + flag=name short= default= required=true + flag=port short= default=5432 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=src-instance-id short= default= required=true + flag=subnet-id short= default= required= + flag=vpc-id short= default= required= + flag=zone short= default= required= +ucloud pgsql db delete use=delete short=Delete UPgSQL instances by instance-id + 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 pgsql db get use=get short=Display details of a UPgSQL instance + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db list use=list short=List UPgSQL instances + flag=instance-id short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db list-machine-type use=list-machine-type short=List available UPgSQL machine types + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db list-version use=list-version short=List available UPgSQL versions + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db price use=price short=Get the price of creating UPgSQL instances + flag=charge-type short= default=Month required= + flag=disk-size-gb short= default=0 required=true + flag=machine-type short= default= required=true + flag=mode short= default=Normal required= + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db reset-password use=reset-password short=Reset the admin password of UPgSQL instances + flag=instance-id short= default=[] required=true + flag=name short= default= required= + flag=password short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db restart use=restart short=Restart UPgSQL instances by instance-id + flag=async short=a default=false required= + flag=force short= default=false required= + flag=instance-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=restart-host short= default=false required= + flag=zone short= default= required= +ucloud pgsql db start use=start short=Start UPgSQL instances by instance-id + flag=async short=a default=false required= + flag=instance-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db stop use=stop short=Stop UPgSQL instances by instance-id + flag=async short=a default=false required= + flag=force short= default=false required= + flag=instance-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=stop-host short= default=false required= + flag=zone short= default= required= +ucloud pgsql db stop-creating-readonly use=stop-creating-readonly short=Stop readonly replicas that are still being created + flag=instance-id short= default=[] required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db update-name use=update-name short=Update the name of a UPgSQL instance + flag=instance-id short= default= required=true + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db update-remark use=update-remark short=Update the remark of a UPgSQL instance + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=remark short= default= required=true + flag=zone short= default= required= +ucloud pgsql db upgrade use=upgrade short=Upgrade disk space and/or machine type of UPgSQL instances + flag=async short=a default=false required= + flag=disk-size-gb short= default=0 required= + flag=instance-id short= default=[] required=true + flag=machine-type short= default= required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql db upgrade-price use=upgrade-price short=Get the price of upgrading a UPgSQL instance + flag=disk-size-gb short= default=0 required=true + flag=instance-id short= default= required=true + flag=machine-type short= default= required=true + flag=mode short= default=Normal required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql log use=log short=List and back up logs of UPgSQL instances +ucloud pgsql log backup use=backup short=Back up the log package of a UPgSQL instance + flag=backup-file short= default= required=true + flag=begin-time short= default= required= + flag=end-time short= default= required= + flag=instance-id short= default= required=true + flag=log-type short= default= required= + flag=name short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql log list use=list short=List logs of a UPgSQL instance within a time range + flag=begin-time short= default= required=true + flag=end-time short= default= required=true + flag=instance-id short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase use=supabase short=Manage USupabase instances (and the MemoryDB AI-memory variant) +ucloud pgsql supabase bandwidth-upgrade-price use=bandwidth-upgrade-price short=Get the price of upgrading external-access bandwidth of a USupabase instance + flag=bandwidth short= default=0 required=true + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase create use=create short=Create a USupabase instance + flag=async short=a default=false required= + flag=charge-type short= default=Month required= + flag=dashboard-name short= default= required=true + flag=dashboard-password short= default= required=true + flag=db-version short= default= required=true + flag=disk-size-gb short= default=0 required=true + flag=label short= default=[] required= + flag=machine-type short= default= required=true + flag=memory-db short= default=false required= + flag=mode short= default=Normal required= + flag=name short= default= required=true + flag=param-group-id short= default=0 required=true + flag=pgsql-password short= default= required=true + flag=pgsql-port short= default=5432 required= + flag=pgsql-user short= default= required=true + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=subnet-id short= default= required=true + flag=supabase-port short= default=8000 required= + flag=vpc-id short= default= required=true + flag=zone short= default= required= +ucloud pgsql supabase create-memory-db use=create-memory-db short=Create a UMemoryDB (AI memory) instance + flag=async short=a default=false required= + flag=charge-type short= default=Month required= + flag=dashboard-name short= default= required=true + flag=dashboard-password short= default= required=true + flag=db-version short= default= required=true + flag=disk-size-gb short= default=0 required=true + flag=label short= default=[] required= + flag=machine-type short= default= required=true + flag=memory-db short= default=false required= + flag=mode short= default=Normal required= + flag=name short= default= required=true + flag=param-group-id short= default=0 required=true + flag=pgsql-password short= default= required=true + flag=pgsql-port short= default=5432 required= + flag=pgsql-user short= default= required=true + flag=project-id short= default= required= + flag=quantity short= default=1 required= + flag=region short= default= required= + flag=subnet-id short= default= required=true + flag=supabase-port short= default=8000 required= + flag=vpc-id short= default= required=true + flag=zone short= default= required= +ucloud pgsql supabase delete use=delete short=Delete a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=yes short=y default=false required= + flag=zone short= default= required= +ucloud pgsql supabase describe use=describe short=Display details of a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase disable-external-access use=disable-external-access short=Disable external access for a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase enable-external-access use=enable-external-access short=Enable external access for a USupabase instance + flag=bandwidth short= default=0 required=true + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase external-price use=external-price short=Get the price of enabling external access for a USupabase instance + flag=bandwidth short= default=0 required=true + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase get-api-key use=get-api-key short=Display the API keys (service key + anon key) of a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase get-storage-config use=get-storage-config short=Display the storage configuration of a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase list use=list short=List USupabase instances + flag=instance-id short= default= required= + flag=limit short= default=0 required= + flag=memory-db short= default=false required= + flag=offset short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase modify-external-access use=modify-external-access short=Modify external-access settings of a USupabase instance + flag=bandwidth short= default=0 required= + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=port short= default=0 required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=white-list short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase reset-password use=reset-password short=Reset the dashboard password of a USupabase instance + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=password short= default= required=true + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase restart use=restart short=Restart a USupabase instance + flag=async short=a default=false required= + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase set-storage-config use=set-storage-config short=Set storage configuration entries of a USupabase instance + flag=config short= default=[] required=true + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase start use=start short=Start a USupabase instance + flag=async short=a default=false required= + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= +ucloud pgsql supabase stop use=stop short=Stop a USupabase instance + flag=async short=a default=false required= + flag=instance-id short= default= required=true + flag=memory-db short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=zone short= default= required= diff --git a/products/pgsql/testdata/completion.golden b/products/pgsql/testdata/completion.golden new file mode 100644 index 0000000000..a1f798a9ef --- /dev/null +++ b/products/pgsql/testdata/completion.golden @@ -0,0 +1,192 @@ +ucloud pgsql backup download backup-id dynamic +ucloud pgsql backup download instance-id dynamic +ucloud pgsql backup download project-id dynamic +ucloud pgsql backup download region dynamic +ucloud pgsql backup download zone dynamic +ucloud pgsql backup list backup-type static auto,manual +ucloud pgsql backup list instance-id dynamic +ucloud pgsql backup list project-id dynamic +ucloud pgsql backup list region dynamic +ucloud pgsql backup list zone dynamic +ucloud pgsql backup strategy instance-id dynamic +ucloud pgsql backup strategy project-id dynamic +ucloud pgsql backup strategy region dynamic +ucloud pgsql backup strategy zone dynamic +ucloud pgsql backup update-strategy instance-id dynamic +ucloud pgsql backup update-strategy project-id dynamic +ucloud pgsql backup update-strategy region dynamic +ucloud pgsql backup update-strategy zone dynamic +ucloud pgsql conf create db-version static postgresql-10.4,postgresql-13.4 +ucloud pgsql conf create project-id dynamic +ucloud pgsql conf create region dynamic +ucloud pgsql conf create src-conf-id dynamic +ucloud pgsql conf create zone dynamic +ucloud pgsql conf delete conf-id dynamic +ucloud pgsql conf delete project-id dynamic +ucloud pgsql conf delete region dynamic +ucloud pgsql conf delete zone dynamic +ucloud pgsql conf describe conf-id dynamic +ucloud pgsql conf describe project-id dynamic +ucloud pgsql conf describe region dynamic +ucloud pgsql conf describe zone dynamic +ucloud pgsql conf download conf-id dynamic +ucloud pgsql conf download project-id dynamic +ucloud pgsql conf download region dynamic +ucloud pgsql conf download zone dynamic +ucloud pgsql conf list project-id dynamic +ucloud pgsql conf list region dynamic +ucloud pgsql conf list zone dynamic +ucloud pgsql conf upload conf-file static +ucloud pgsql conf upload db-version static postgresql-10.4,postgresql-13.4 +ucloud pgsql conf upload project-id dynamic +ucloud pgsql conf upload region dynamic +ucloud pgsql conf upload zone dynamic +ucloud pgsql db create machine-type dynamic +ucloud pgsql db create mode static HA,Normal +ucloud pgsql db create param-group-id dynamic +ucloud pgsql db create project-id dynamic +ucloud pgsql db create region dynamic +ucloud pgsql db create subnet-id dynamic +ucloud pgsql db create version static postgresql-10.4,postgresql-13.4 +ucloud pgsql db create vpc-id dynamic +ucloud pgsql db create zone dynamic +ucloud pgsql db create-readonly machine-type dynamic +ucloud pgsql db create-readonly project-id dynamic +ucloud pgsql db create-readonly region dynamic +ucloud pgsql db create-readonly src-instance-id dynamic +ucloud pgsql db create-readonly subnet-id dynamic +ucloud pgsql db create-readonly vpc-id dynamic +ucloud pgsql db create-readonly zone dynamic +ucloud pgsql db delete instance-id dynamic +ucloud pgsql db delete project-id dynamic +ucloud pgsql db delete region dynamic +ucloud pgsql db delete zone dynamic +ucloud pgsql db get instance-id dynamic +ucloud pgsql db get project-id dynamic +ucloud pgsql db get region dynamic +ucloud pgsql db get zone dynamic +ucloud pgsql db list instance-id dynamic +ucloud pgsql db list project-id dynamic +ucloud pgsql db list region dynamic +ucloud pgsql db list zone dynamic +ucloud pgsql db list-machine-type project-id dynamic +ucloud pgsql db list-machine-type region dynamic +ucloud pgsql db list-machine-type zone dynamic +ucloud pgsql db list-version project-id dynamic +ucloud pgsql db list-version region dynamic +ucloud pgsql db list-version zone dynamic +ucloud pgsql db price charge-type static Dynamic,Month,Year +ucloud pgsql db price machine-type dynamic +ucloud pgsql db price mode static HA,Normal +ucloud pgsql db price project-id dynamic +ucloud pgsql db price region dynamic +ucloud pgsql db price zone dynamic +ucloud pgsql db reset-password instance-id dynamic +ucloud pgsql db reset-password project-id dynamic +ucloud pgsql db reset-password region dynamic +ucloud pgsql db reset-password zone dynamic +ucloud pgsql db restart force static false,true +ucloud pgsql db restart instance-id dynamic +ucloud pgsql db restart project-id dynamic +ucloud pgsql db restart region dynamic +ucloud pgsql db restart restart-host static false,true +ucloud pgsql db restart zone dynamic +ucloud pgsql db start instance-id dynamic +ucloud pgsql db start project-id dynamic +ucloud pgsql db start region dynamic +ucloud pgsql db start zone dynamic +ucloud pgsql db stop force static false,true +ucloud pgsql db stop instance-id dynamic +ucloud pgsql db stop project-id dynamic +ucloud pgsql db stop region dynamic +ucloud pgsql db stop stop-host static false,true +ucloud pgsql db stop zone dynamic +ucloud pgsql db stop-creating-readonly instance-id dynamic +ucloud pgsql db stop-creating-readonly project-id dynamic +ucloud pgsql db stop-creating-readonly region dynamic +ucloud pgsql db stop-creating-readonly zone dynamic +ucloud pgsql db update-name instance-id dynamic +ucloud pgsql db update-name project-id dynamic +ucloud pgsql db update-name region dynamic +ucloud pgsql db update-name zone dynamic +ucloud pgsql db update-remark instance-id dynamic +ucloud pgsql db update-remark project-id dynamic +ucloud pgsql db update-remark region dynamic +ucloud pgsql db update-remark zone dynamic +ucloud pgsql db upgrade instance-id dynamic +ucloud pgsql db upgrade machine-type dynamic +ucloud pgsql db upgrade project-id dynamic +ucloud pgsql db upgrade region dynamic +ucloud pgsql db upgrade zone dynamic +ucloud pgsql db upgrade-price instance-id dynamic +ucloud pgsql db upgrade-price machine-type dynamic +ucloud pgsql db upgrade-price mode static HA,Normal +ucloud pgsql db upgrade-price project-id dynamic +ucloud pgsql db upgrade-price region dynamic +ucloud pgsql db upgrade-price zone dynamic +ucloud pgsql log backup instance-id dynamic +ucloud pgsql log backup log-type static error,slow +ucloud pgsql log backup project-id dynamic +ucloud pgsql log backup region dynamic +ucloud pgsql log backup zone dynamic +ucloud pgsql log list instance-id dynamic +ucloud pgsql log list project-id dynamic +ucloud pgsql log list region dynamic +ucloud pgsql log list zone dynamic +ucloud pgsql supabase bandwidth-upgrade-price project-id dynamic +ucloud pgsql supabase bandwidth-upgrade-price region dynamic +ucloud pgsql supabase bandwidth-upgrade-price zone dynamic +ucloud pgsql supabase create charge-type static Dynamic,Month,Year +ucloud pgsql supabase create db-version static postgresql-10.4,postgresql-13.4 +ucloud pgsql supabase create mode static HA,Normal +ucloud pgsql supabase create project-id dynamic +ucloud pgsql supabase create region dynamic +ucloud pgsql supabase create zone dynamic +ucloud pgsql supabase create-memory-db charge-type static Dynamic,Month,Year +ucloud pgsql supabase create-memory-db db-version static postgresql-10.4,postgresql-13.4 +ucloud pgsql supabase create-memory-db mode static HA,Normal +ucloud pgsql supabase create-memory-db project-id dynamic +ucloud pgsql supabase create-memory-db region dynamic +ucloud pgsql supabase create-memory-db zone dynamic +ucloud pgsql supabase delete project-id dynamic +ucloud pgsql supabase delete region dynamic +ucloud pgsql supabase delete zone dynamic +ucloud pgsql supabase describe project-id dynamic +ucloud pgsql supabase describe region dynamic +ucloud pgsql supabase describe zone dynamic +ucloud pgsql supabase disable-external-access project-id dynamic +ucloud pgsql supabase disable-external-access region dynamic +ucloud pgsql supabase disable-external-access zone dynamic +ucloud pgsql supabase enable-external-access project-id dynamic +ucloud pgsql supabase enable-external-access region dynamic +ucloud pgsql supabase enable-external-access zone dynamic +ucloud pgsql supabase external-price project-id dynamic +ucloud pgsql supabase external-price region dynamic +ucloud pgsql supabase external-price zone dynamic +ucloud pgsql supabase get-api-key project-id dynamic +ucloud pgsql supabase get-api-key region dynamic +ucloud pgsql supabase get-api-key zone dynamic +ucloud pgsql supabase get-storage-config project-id dynamic +ucloud pgsql supabase get-storage-config region dynamic +ucloud pgsql supabase get-storage-config zone dynamic +ucloud pgsql supabase list project-id dynamic +ucloud pgsql supabase list region dynamic +ucloud pgsql supabase list zone dynamic +ucloud pgsql supabase modify-external-access project-id dynamic +ucloud pgsql supabase modify-external-access region dynamic +ucloud pgsql supabase modify-external-access zone dynamic +ucloud pgsql supabase reset-password project-id dynamic +ucloud pgsql supabase reset-password region dynamic +ucloud pgsql supabase reset-password zone dynamic +ucloud pgsql supabase restart project-id dynamic +ucloud pgsql supabase restart region dynamic +ucloud pgsql supabase restart zone dynamic +ucloud pgsql supabase set-storage-config project-id dynamic +ucloud pgsql supabase set-storage-config region dynamic +ucloud pgsql supabase set-storage-config zone dynamic +ucloud pgsql supabase start project-id dynamic +ucloud pgsql supabase start region dynamic +ucloud pgsql supabase start zone dynamic +ucloud pgsql supabase stop project-id dynamic +ucloud pgsql supabase stop region dynamic +ucloud pgsql supabase stop zone dynamic