diff --git a/cmd/products.gen.go b/cmd/products.gen.go index ba87a91d01..9c9b58c2a3 100644 --- a/cmd/products.gen.go +++ b/cmd/products.gen.go @@ -18,6 +18,7 @@ import ( "github.com/ucloud/ucloud-cli/products/sqlserver" "github.com/ucloud/ucloud-cli/products/subnet" "github.com/ucloud/ucloud-cli/products/uclickhouse" + "github.com/ucloud/ucloud-cli/products/udac" "github.com/ucloud/ucloud-cli/products/uddos" "github.com/ucloud/ucloud-cli/products/udisk" "github.com/ucloud/ucloud-cli/products/udpn" @@ -55,6 +56,7 @@ func registeredProducts() []cli.Product { sqlserver.New(), subnet.New(), uclickhouse.New(), + udac.New(), uddos.New(), udisk.New(), udpn.New(), diff --git a/products/udac/internal/udac/cmd.go b/products/udac/internal/udac/cmd.go new file mode 100644 index 0000000000..ee148284f5 --- /dev/null +++ b/products/udac/internal/udac/cmd.go @@ -0,0 +1,23 @@ +package udac + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" +) + +// NewCommand builds the `udac` root command and mounts the subcommands. +func NewCommand(ctx *cli.Context) *cobra.Command { + cmd := &cobra.Command{ + Use: "udac", + Short: "Manage Database Autonomous Center (UDAC) instances", + Long: "Import, export, and list database instances in the Database Autonomous Center (UDAC).", + Args: cobra.NoArgs, + } + + cmd.AddCommand(newImport(ctx)) + cmd.AddCommand(newExport(ctx)) + cmd.AddCommand(newList(ctx)) + + return cmd +} diff --git a/products/udac/internal/udac/export.go b/products/udac/internal/udac/export.go new file mode 100644 index 0000000000..6179df0de4 --- /dev/null +++ b/products/udac/internal/udac/export.go @@ -0,0 +1,204 @@ +package udac + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/ucloud/ucloud-sdk-go/services/uaccount" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// actionForExportType 根据实例类型返回从 UDAC 移除对应的 Action 名。 +func actionForExportType(instanceType string) (string, error) { + switch instanceType { + case "mysql": + return "RemoveUDACMySQLInstances", nil + case "mongodb": + return "RemoveUDACUMongoDBClusters", nil + default: + return "", fmt.Errorf("unsupported instance type: %s, supported: %v", instanceType, SupportedTypes) + } +} + +// exportInstances 调用 UDAC 移除 MySQL API,所有实例共用同一个 zone。 +// 走 SDK 默认 FormEncoder(扁平化格式),MySQL 后端接受。 +func exportInstances(ctx *cli.Context, action, projectID, zone string, instanceIDs []string) (map[string]interface{}, error) { + instanceInfoSet := make([]interface{}, 0, len(instanceIDs)) + for _, id := range instanceIDs { + instanceInfoSet = append(instanceInfoSet, map[string]interface{}{ + "ID": id, + "Zone": zone, + }) + } + + params := map[string]interface{}{ + "Action": action, + "ProjectId": projectID, + "InstanceInfoSet": instanceInfoSet, + } + + client := cli.NewServiceClient(ctx, uaccount.NewClient) + req := client.NewGenericRequest() + if err := req.SetPayload(params); 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 +} + +func exportMongoDBClusters(ctx *cli.Context, action, projectID, region string, clusterIDs []string) (map[string]interface{}, error) { + mongoDBClusterSet := make([]interface{}, 0, len(clusterIDs)) + for _, id := range clusterIDs { + mongoDBClusterSet = append(mongoDBClusterSet, map[string]interface{}{ + "ClusterId": id, + "Region": region, + }) + } + + params := map[string]interface{}{ + "Action": action, + "ProjectId": projectID, + "Region": region, + "MongoDBClusterSet": mongoDBClusterSet, + } + + client := cli.NewServiceClient(ctx, uaccount.NewClient) + req := client.NewGenericRequest() + if err := req.SetPayload(params); err != nil { + return nil, fmt.Errorf("set payload: %w", err) + } + req.SetEncoder(request.NewJSONEncoder(client.GetConfig(), client.GetCredential())) + resp, err := client.GenericInvoke(req) + if err != nil { + return nil, err + } + return resp.GetPayload(), nil +} + +// newExport implements `ucloud udac export` +// MySQL 必填:--udb-id, --type=mysql, --zone, --project-id +// MongoDB 必填:--udb-id, --type=mongodb, --region, --project-id +// export 是从 UDAC 移除实例(不是导出数据),语义同 import 的反向操作。 +func newExport(ctx *cli.Context) *cobra.Command { + var instanceIDs []string + var instanceType string + var common request.CommonBase + + cmd := &cobra.Command{ + Use: "export", + Short: "Export database instances from UDAC", + Long: `Export existing database instances from the Database Autonomous Center (UDAC). + +You must specify the instance type via --type. Supported types: mysql, mongodb. + +Required flags: + mysql: --udb-id, --type=mysql, --project-id (--zone falls back to config default) + mongodb: --udb-id, --type=mongodb, --project-id (--region falls back to config default) + +--project-id, --region, --zone fall back to config defaults (default-project-id, +default-region, default-zone). + +This is a synchronous operation: the command returns after the export API responds.`, + Run: func(c *cobra.Command, args []string) { + // 1. 开头单独校验 udb-id 和 type(早失败) + if len(instanceIDs) == 0 { + ctx.HandleError(fmt.Errorf("required flag(s) not set: %s", resourceIDFlag)) + return + } + if instanceType == "" { + ctx.HandleError(fmt.Errorf("required flag(s) not set: type")) + return + } + + // 2. 类型校验 + Action 选择 + action, err := actionForExportType(instanceType) + if err != nil { + ctx.HandleError(err) + return + } + + // 3. 从 common 取绑定值 + projectID := common.GetProjectId() + region := common.GetRegion() + zone := common.GetZone() + + // 4. 类型相关必填校验(配置默认值兜底,空时报错) + var missing []string + if projectID == "" { + missing = append(missing, "project-id") + } + if instanceType == "mysql" && zone == "" { + missing = append(missing, "zone") + } + if instanceType == "mongodb" && region == "" { + missing = append(missing, "region") + } + if len(missing) > 0 { + ctx.HandleError(fmt.Errorf("required flag(s) not set: %s", strings.Join(missing, ", "))) + return + } + + // 5. 归一化 instance ID(支持 "udb-xxx/instance-name" 格式) + for i, id := range instanceIDs { + instanceIDs[i] = ctx.PickResourceID(id) + } + + // 6. 按类型分流调用 API + var payload map[string]interface{} + if instanceType == "mongodb" { + payload, err = exportMongoDBClusters(ctx, action, projectID, region, instanceIDs) + } else { + payload, err = exportInstances(ctx, action, projectID, zone, instanceIDs) + } + if err != nil { + ctx.HandleError(err) + return + } + if len(payload) == 0 { + ctx.HandleError(fmt.Errorf("empty response from server")) + return + } + + // 7. 输出 + w := ctx.ProgressWriter() + for _, id := range instanceIDs { + if instanceType == "mongodb" { + fmt.Fprintf(w, "%s[%s] exported successfully (type: %s, region: %s)\n", productName, id, instanceType, region) + } else { + fmt.Fprintf(w, "%s[%s] exported successfully (type: %s, zone: %s)\n", productName, id, instanceType, zone) + } + } + results := make([]cli.OpResultRow, 0, len(instanceIDs)) + for _, id := range instanceIDs { + results = append(results, cli.OpResultRow{ + ResourceID: id, + Action: "export", + Status: "Exported", + }) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&instanceIDs, resourceIDFlag, nil, "Required. Instance ID(s) to export. Repeatable.") + flags.StringVar(&instanceType, typeFlag, "", "Required. Instance type: mysql, mongodb.") + + // 公共参数绑定:project-id/region/zone 都用配置默认值兜底 + ctx.BindRegion(cmd, &common) + ctx.BindZone(cmd, &common) + ctx.BindProjectID(cmd, &common) + + command.SetFlagValues(cmd, typeFlag, SupportedTypes...) + + return cmd +} diff --git a/products/udac/internal/udac/import.go b/products/udac/internal/udac/import.go new file mode 100644 index 0000000000..4076fa6bef --- /dev/null +++ b/products/udac/internal/udac/import.go @@ -0,0 +1,203 @@ +package udac + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/ucloud/ucloud-sdk-go/services/uaccount" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// actionForImportType 根据实例类型返回导入对应的 UDAC Action 名。 +func actionForImportType(instanceType string) (string, error) { + switch instanceType { + case "mysql": + return "AddUDACMySQLInstances", nil + case "mongodb": + return "AddUDACUMongoDBClusters", nil + default: + return "", fmt.Errorf("unsupported instance type: %s, supported: %v", instanceType, SupportedTypes) + } +} + +// importInstances 调用 UDAC 导入 MySQL API,所有实例共用同一个 zone。 +// 走 SDK 默认 FormEncoder(扁平化格式),MySQL 后端接受。 +func importInstances(ctx *cli.Context, action, projectID, zone string, instanceIDs []string) (map[string]interface{}, error) { + instanceInfoSet := make([]interface{}, 0, len(instanceIDs)) + for _, id := range instanceIDs { + instanceInfoSet = append(instanceInfoSet, map[string]interface{}{ + "ID": id, + "Zone": zone, + }) + } + + params := map[string]interface{}{ + "Action": action, + "ProjectId": projectID, + "InstanceInfoSet": instanceInfoSet, + } + + client := cli.NewServiceClient(ctx, uaccount.NewClient) + req := client.NewGenericRequest() + if err := req.SetPayload(params); 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 +} + +func importMongoDBClusters(ctx *cli.Context, action, projectID, region string, clusterIDs []string) (map[string]interface{}, error) { + mongoDBClusterSet := make([]interface{}, 0, len(clusterIDs)) + for _, id := range clusterIDs { + mongoDBClusterSet = append(mongoDBClusterSet, map[string]interface{}{ + "ClusterId": id, + "Region": region, + }) + } + + params := map[string]interface{}{ + "Action": action, + "ProjectId": projectID, + "Region": region, + "MongoDBClusterSet": mongoDBClusterSet, + } + + client := cli.NewServiceClient(ctx, uaccount.NewClient) + req := client.NewGenericRequest() + if err := req.SetPayload(params); err != nil { + return nil, fmt.Errorf("set payload: %w", err) + } + req.SetEncoder(request.NewJSONEncoder(client.GetConfig(), client.GetCredential())) + resp, err := client.GenericInvoke(req) + if err != nil { + return nil, err + } + return resp.GetPayload(), nil +} + +// newImport implements `ucloud udac import` +// MySQL 必填:--udb-id, --type=mysql, --zone, --project-id +// MongoDB 必填:--udb-id, --type=mongodb, --region, --project-id +func newImport(ctx *cli.Context) *cobra.Command { + var instanceIDs []string + var instanceType string + var common request.CommonBase + + cmd := &cobra.Command{ + Use: "import", + Short: "Import database instances into UDAC", + Long: `Import existing database instances into the Database Autonomous Center (UDAC) for autonomous management. + +You must specify the instance type via --type. Supported types: mysql, mongodb. + +Required flags: + mysql: --udb-id, --type=mysql, --project-id (--zone falls back to config default) + mongodb: --udb-id, --type=mongodb, --project-id (--region falls back to config default) + +--project-id, --region, --zone fall back to config defaults (default-project-id, +default-region, default-zone). + +This is a synchronous operation: the command returns after the import API responds.`, + Run: func(c *cobra.Command, args []string) { + // 1. 开头单独校验 udb-id 和 type(早失败) + if len(instanceIDs) == 0 { + ctx.HandleError(fmt.Errorf("required flag(s) not set: %s", resourceIDFlag)) + return + } + if instanceType == "" { + ctx.HandleError(fmt.Errorf("required flag(s) not set: type")) + return + } + + // 2. 类型校验 + Action 选择 + action, err := actionForImportType(instanceType) + if err != nil { + ctx.HandleError(err) + return + } + + // 3. 从 common 取绑定值 + projectID := common.GetProjectId() + region := common.GetRegion() + zone := common.GetZone() + + // 4. 类型相关必填校验(配置默认值兜底,空时报错) + var missing []string + if projectID == "" { + missing = append(missing, "project-id") + } + if instanceType == "mysql" && zone == "" { + missing = append(missing, "zone") + } + if instanceType == "mongodb" && region == "" { + missing = append(missing, "region") + } + if len(missing) > 0 { + ctx.HandleError(fmt.Errorf("required flag(s) not set: %s", strings.Join(missing, ", "))) + return + } + + // 5. 归一化 instance ID(支持 "udb-xxx/instance-name" 格式) + for i, id := range instanceIDs { + instanceIDs[i] = ctx.PickResourceID(id) + } + + // 6. 按类型分流调用 API + var payload map[string]interface{} + if instanceType == "mongodb" { + payload, err = importMongoDBClusters(ctx, action, projectID, region, instanceIDs) + } else { + payload, err = importInstances(ctx, action, projectID, zone, instanceIDs) + } + if err != nil { + ctx.HandleError(err) + return + } + if len(payload) == 0 { + ctx.HandleError(fmt.Errorf("empty response from server")) + return + } + + // 7. 输出 + w := ctx.ProgressWriter() + for _, id := range instanceIDs { + if instanceType == "mongodb" { + fmt.Fprintf(w, "%s[%s] imported successfully (type: %s, region: %s)\n", productName, id, instanceType, region) + } else { + fmt.Fprintf(w, "%s[%s] imported successfully (type: %s, zone: %s)\n", productName, id, instanceType, zone) + } + } + results := make([]cli.OpResultRow, 0, len(instanceIDs)) + for _, id := range instanceIDs { + results = append(results, cli.OpResultRow{ + ResourceID: id, + Action: "import", + Status: "Imported", + }) + } + ctx.EmitResult(results...) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringSliceVar(&instanceIDs, resourceIDFlag, nil, "Required. Instance ID(s) to import. Repeatable.") + flags.StringVar(&instanceType, typeFlag, "", "Required. Instance type: mysql, mongodb.") + + // 公共参数绑定:project-id/region/zone 都用配置默认值兜底 + ctx.BindRegion(cmd, &common) + ctx.BindZone(cmd, &common) + ctx.BindProjectID(cmd, &common) + + command.SetFlagValues(cmd, typeFlag, SupportedTypes...) + + return cmd +} diff --git a/products/udac/internal/udac/list.go b/products/udac/internal/udac/list.go new file mode 100644 index 0000000000..1274b8cd49 --- /dev/null +++ b/products/udac/internal/udac/list.go @@ -0,0 +1,299 @@ +package udac + +import ( + "fmt" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/ucloud/ucloud-sdk-go/services/uaccount" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" + + "github.com/ucloud/ucloud-cli/pkg/cli" + "github.com/ucloud/ucloud-cli/pkg/command" +) + +// actionForType 根据实例类型返回对应的 UDAC Action 名。 +func actionForType(instanceType string) (string, error) { + switch instanceType { + case "mysql": + return "ListUDACMySQLInstance", nil + case "mongodb": + return "ListUDACUMongoDBClusters", nil + default: + return "", fmt.Errorf("unsupported instance type: %s, supported: %v", instanceType, SupportedTypes) + } +} + +// regionFromZone 从 zone 推导 region。 +// UCloud zone 命名规则:{region}-{suffix},例如 cn-bj2-02 → cn-bj2、hk-02 → hk。 +func regionFromZone(zone string) string { + if i := strings.LastIndex(zone, "-"); i > 0 { + return zone[:i] + } + return zone +} + +// newInstanceRow 从 API 返回的 map 构造一行展示数据。 +// overrideRegion 非空时用用户传入值,否则优先读 API 返回的 Region,再兜底从 Zone 推导。 +// MongoDB 响应字段:ClusterId/Name/Region/State/JoinTime(无 Zone)。 +// MySQL 响应字段:ID 或 InstanceId/Name/Zone/State/Status/JoinTime(无 Region)。 +func newInstanceRow(m map[string]interface{}, instanceType, overrideRegion string) (importedInstanceRow, bool) { + id := firstString(m, "ClusterId", "InstanceId", "ID") + if id == "" { + return importedInstanceRow{}, false + } + zoneVal := getString(m, "Zone") + regionVal := getString(m, "Region") + if regionVal == "" { + regionVal = overrideRegion + } + if regionVal == "" { + regionVal = regionFromZone(zoneVal) + } + status := firstString(m, "State", "Status") + joinTime := getInt64(m, "JoinTime") + importTime := "" + if joinTime > 0 { + importTime = time.Unix(joinTime, 0).Format(time.RFC3339) + } + return importedInstanceRow{ + ResourceID: id, + InstanceID: id, + Name: getString(m, "Name"), + Type: instanceType, + Status: status, + ImportTime: importTime, + Region: regionVal, + Zone: zoneVal, + }, true +} + +// firstString 按顺序尝试多个 key,返回第一个非空字符串。 +func firstString(m map[string]interface{}, keys ...string) string { + for _, k := range keys { + if v := getString(m, k); v != "" { + return v + } + } + return "" +} + +// fetchUDACInstances 调用 UDAC list API,返回账号下全部实例。 +func fetchUDACInstances(ctx *cli.Context, action, projectID string) ([]map[string]interface{}, error) { + params := map[string]interface{}{ + "Action": action, + "ProjectId": projectID, + } + + client := cli.NewServiceClient(ctx, uaccount.NewClient) + req := client.NewGenericRequest() + if err := req.SetPayload(params); err != nil { + return nil, fmt.Errorf("set payload: %w", err) + } + resp, err := client.GenericInvoke(req) + if err != nil { + return nil, err + } + payload := resp.GetPayload() + + var raw []interface{} + for _, key := range []string{"InstanceInfoSet", "Instances", "DataSet"} { + if val, ok := payload[key].([]interface{}); ok { + raw = val + break + } + } + out := make([]map[string]interface{}, 0, len(raw)) + for _, item := range raw { + if m, ok := item.(map[string]interface{}); ok { + out = append(out, m) + } + } + return out, nil +} + +// newList implements `ucloud udac list` +// --project-id 必填(配置默认值兜底);其他可选。 +func newList(ctx *cli.Context) *cobra.Command { + var instanceID, instanceType, statusFilter string + var allRegions bool + var common request.CommonBase + + cmd := &cobra.Command{ + Use: "list", + Short: "List imported database instances in UDAC", + Long: `List database instances that have been imported into the Database Autonomous Center (UDAC). + +Required flag: --project-id (falls back to default-project-id from config if set). + +Optional filters: + --type Instance type: mysql, mongodb + --region Filter by region. Defaults to config's default-region. + --zone Filter by zone. If omitted, list across all zones in the region. + --udb-id List only the specified instance. + --status Filter by status (e.g., Running, Failed). + --all-regions List instances across all regions (ignore --region and config default). + +When both --region and --zone are specified, the zone must belong to the region.`, + Run: func(c *cobra.Command, args []string) { + // 1. 必填校验:project-id(配置默认值兜底) + projectID := common.GetProjectId() + if projectID == "" { + ctx.HandleError(fmt.Errorf("required flag(s) not set: project-id")) + return + } + + // 2. 类型校验 + Action 选择 + action, err := actionForType(instanceType) + if err != nil { + ctx.HandleError(err) + return + } + + // 3. 确定 region 过滤值:--all-regions 优先级最高 + if allRegions && c.Flags().Changed("region") { + ctx.HandleError(fmt.Errorf("--all-regions and --region are mutually exclusive")) + return + } + region := common.GetRegion() + zone := common.GetZone() + if allRegions { + region = "" + } + + // 4. region/zone 一致性校验 + if region != "" && zone != "" && !strings.HasPrefix(zone, region+"-") { + ctx.HandleError(fmt.Errorf("zone %s does not belong to region %s", zone, region)) + return + } + + // mongodb 响应无 Zone 字段,--zone 过滤会静默清空 + if instanceType == "mongodb" && zone != "" { + ctx.HandleError(fmt.Errorf("--zone is not supported for mongodb, use --region instead")) + return + } + + // 5. 拉取全部实例(API 只带 ProjectId,不传 Region/Zone) + instances, err := fetchUDACInstances(ctx, action, projectID) + if err != nil { + ctx.HandleError(err) + return + } + + // 6. 客户端过滤 + instanceID = ctx.PickResourceID(instanceID) + rows := make([]importedInstanceRow, 0, len(instances)) + for _, m := range instances { + if instanceID != "" && firstString(m, "ClusterId", "InstanceId", "ID") != instanceID { + continue + } + zoneVal := getString(m, "Zone") + if zone != "" && zoneVal != zone { + continue + } + if region != "" { + // 优先用 API 返回的 Region,其次从 Zone 推导 + instanceRegion := getString(m, "Region") + if instanceRegion == "" { + instanceRegion = regionFromZone(zoneVal) + } + if instanceRegion != region { + continue + } + } + if statusFilter != "" && firstString(m, "State", "Status") != statusFilter { + continue + } + if row, ok := newInstanceRow(m, instanceType, region); ok { + rows = append(rows, row) + } + } + + // 7. 输出(空列表正常输出空表,不报错) + ctx.PrintList(rows) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + flags.StringVar(&instanceID, resourceIDFlag, "", "Optional. List only the specified instance.") + flags.StringVar(&instanceType, typeFlag, "mysql", "Optional. Instance type: mysql, mongodb.") + flags.StringVar(&statusFilter, "status", "", "Optional. Filter by status (e.g., Running, Failed).") + flags.BoolVar(&allRegions, "all-regions", false, "Optional. List instances across all regions (ignore --region and config default).") + + // 公共参数绑定:region/zone/project-id 都用配置默认值兜底 + ctx.BindRegion(cmd, &common) + ctx.BindZoneEmpty(cmd, &common) + ctx.BindProjectID(cmd, &common) + + command.SetFlagValues(cmd, typeFlag, SupportedTypes...) + command.SetCompletion(cmd, resourceIDFlag, func() []string { + return listImportedInstanceIDs(ctx, instanceType, common.GetRegion(), common.GetZone(), common.GetProjectId()) + }) + + return cmd +} + +func listImportedInstanceIDs(ctx *cli.Context, instanceType, region, zone, projectID string) []string { + action, err := actionForType(instanceType) + if err != nil { + return nil + } + if instanceType == "mongodb" && zone != "" { + return nil + } + instances, err := fetchUDACInstances(ctx, action, projectID) + if err != nil { + return nil + } + out := make([]string, 0, len(instances)) + for _, m := range instances { + zoneVal := getString(m, "Zone") + if zone != "" && zoneVal != zone { + continue + } + if region != "" { + instanceRegion := getString(m, "Region") + if instanceRegion == "" { + instanceRegion = regionFromZone(zoneVal) + } + if instanceRegion != region { + continue + } + } + id := firstString(m, "ClusterId", "InstanceId", "ID") + if id == "" { + continue + } + if name := getString(m, "Name"); name != "" { + out = append(out, id+"/"+name) + } else { + out = append(out, id) + } + } + return out +} + +// getString 从 map 中安全获取字符串值 +func getString(m map[string]interface{}, key string) string { + if val, ok := m[key].(string); ok { + return val + } + return "" +} + +// getInt64 从 map 中安全获取 int64 值(兼容 float64/int64/int) +func getInt64(m map[string]interface{}, key string) int64 { + if val, ok := m[key].(float64); ok { + return int64(val) + } + if val, ok := m[key].(int64); ok { + return int64(val) + } + if val, ok := m[key].(int); ok { + return int64(val) + } + return 0 +} diff --git a/products/udac/internal/udac/rows.go b/products/udac/internal/udac/rows.go new file mode 100644 index 0000000000..2c605b76a2 --- /dev/null +++ b/products/udac/internal/udac/rows.go @@ -0,0 +1,13 @@ +package udac + +// importedInstanceRow is the row format for list output +type importedInstanceRow struct { + ResourceID string `json:"resourceId" header:"ResourceID"` + InstanceID string `json:"instanceId" header:"InstanceID"` + Name string `json:"name" header:"Name"` + Type string `json:"type" header:"Type"` + Status string `json:"status" header:"Status"` + ImportTime string `json:"importTime" header:"ImportTime"` + Region string `json:"region" header:"Region"` + Zone string `json:"zone" header:"Zone"` +} diff --git a/products/udac/internal/udac/status.go b/products/udac/internal/udac/status.go new file mode 100644 index 0000000000..e2a54d62aa --- /dev/null +++ b/products/udac/internal/udac/status.go @@ -0,0 +1,10 @@ +package udac + +const ( + productName = "udac" + resourceIDFlag = "udb-id" // list 命令的 --udb-id flag + typeFlag = "type" // 实例类型 flag +) + +// SupportedTypes 支持的实例类型列表 +var SupportedTypes = []string{"mysql", "mongodb"} diff --git a/products/udac/product.go b/products/udac/product.go new file mode 100644 index 0000000000..b051f8e869 --- /dev/null +++ b/products/udac/product.go @@ -0,0 +1,20 @@ +package udac + +import ( + "github.com/spf13/cobra" + + "github.com/ucloud/ucloud-cli/pkg/cli" + internaludac "github.com/ucloud/ucloud-cli/products/udac/internal/udac" +) + +type product struct{} + +func New() cli.Product { return product{} } + +func (product) Metadata() cli.Metadata { + return cli.Metadata{Name: "udac", Commands: []string{"udac"}} +} + +func (product) NewCommand(ctx *cli.Context) []*cobra.Command { + return []*cobra.Command{internaludac.NewCommand(ctx)} +} diff --git a/products/udac/product.yaml b/products/udac/product.yaml new file mode 100644 index 0000000000..874bd471f8 --- /dev/null +++ b/products/udac/product.yaml @@ -0,0 +1,6 @@ +name: udac +owners: + - nzr1122 +commands: + - udac +enabled: true diff --git a/products/udac/testdata/cmdtree.golden b/products/udac/testdata/cmdtree.golden new file mode 100644 index 0000000000..1b03224684 --- /dev/null +++ b/products/udac/testdata/cmdtree.golden @@ -0,0 +1,21 @@ +ucloud udac use=udac short=Manage Database Autonomous Center (UDAC) instances +ucloud udac export use=export short=Export database instances from UDAC + flag=project-id short= default= required= + flag=region short= default= required= + flag=type short= default= required= + flag=udb-id short= default=[] required= + flag=zone short= default= required= +ucloud udac import use=import short=Import database instances into UDAC + flag=project-id short= default= required= + flag=region short= default= required= + flag=type short= default= required= + flag=udb-id short= default=[] required= + flag=zone short= default= required= +ucloud udac list use=list short=List imported database instances in UDAC + flag=all-regions short= default=false required= + flag=project-id short= default= required= + flag=region short= default= required= + flag=status short= default= required= + flag=type short= default=mysql required= + flag=udb-id short= default= required= + flag=zone short= default= required= diff --git a/products/udac/testdata/completion.golden b/products/udac/testdata/completion.golden new file mode 100644 index 0000000000..26ee94763f --- /dev/null +++ b/products/udac/testdata/completion.golden @@ -0,0 +1,13 @@ +ucloud udac export project-id dynamic +ucloud udac export region dynamic +ucloud udac export type static mongodb,mysql +ucloud udac export zone dynamic +ucloud udac import project-id dynamic +ucloud udac import region dynamic +ucloud udac import type static mongodb,mysql +ucloud udac import zone dynamic +ucloud udac list project-id dynamic +ucloud udac list region dynamic +ucloud udac list type static mongodb,mysql +ucloud udac list udb-id dynamic +ucloud udac list zone dynamic