feat(cmd/ry): migrate rainyun-cli to SDK-backed cmd/ry

- 删除 CLI 自带 provider/HTTP 层,全部改调 sdk.New(apiKey)
- config 简化为单一 apikey + output
- 补 SDK 4 个缺口端点(domain list / dns list / bucket-by-instance / expense orders)
- 修 output --raw bug(新增 raw case)
- 命令 ID 参数统一 int,显示 model ID 保持 string
- reinstall flag 从 --os 改 --os-id int
This commit is contained in:
2026-08-13 00:48:28 +08:00
parent b15e78fd4d
commit bd7b87d9c7
8 changed files with 710 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
package billing
import (
"strconv"
"time"
"github.com/XingfenD/rainyun_api_go_sdk/apis/common"
"github.com/XingfenD/rainyun_api_go_sdk/apis/expense"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output"
"github.com/XingfenD/rainyun_api_go_sdk/sdk"
"github.com/spf13/cobra"
)
func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command {
billingCmd := &cobra.Command{
Use: "billing",
Short: "Billing and orders",
}
ordersCmd := &cobra.Command{
Use: "orders",
Short: "List orders",
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := (*rySDK).GetOrdersList(&expense.GetOrdersListRequest{
Options: common.StandQueryParameters{Page: 1, PerPage: 50},
})
if err != nil {
return err
}
orders := make([]model.Order, len(resp.Data.Records))
for i, r := range resp.Data.Records {
t, _ := time.Parse("2006-01-02 15:04:05", r.CreateDate)
orders[i] = model.Order{
ID: strconv.Itoa(r.ID),
Product: r.ProductName,
Amount: r.Amount,
Status: r.Status,
Created: t,
}
}
return (*out).Print(orders)
},
}
billingCmd.AddCommand(ordersCmd)
return billingCmd
}
+75
View File
@@ -0,0 +1,75 @@
package configcmd
import (
"fmt"
"strings"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/config"
"github.com/spf13/cobra"
)
func Cmd(cfg **config.Config, cfgPath string) *cobra.Command {
configCmd := &cobra.Command{
Use: "config",
Short: "Manage ry configuration",
Annotations: map[string]string{"skipSDK": "true"},
}
configShowCmd := &cobra.Command{
Use: "show",
Short: "Show current configuration",
Annotations: map[string]string{"skipSDK": "true"},
RunE: func(cmd *cobra.Command, args []string) error {
c := *cfg
fmt.Printf("Config file: %s\n", cfgPath)
masked := c.APIKey
if len(masked) > 4 {
masked = masked[:2] + strings.Repeat("*", len(masked)-4) + masked[len(masked)-2:]
}
fmt.Printf("API key: %s\n", masked)
fmt.Printf("Output: %s\n", c.Output)
return nil
},
}
configSetCmd := &cobra.Command{
Use: "set <key> <value>",
Short: "Set a config value",
Args: cobra.ExactArgs(2),
Annotations: map[string]string{"skipSDK": "true"},
RunE: func(cmd *cobra.Command, args []string) error {
key, value := args[0], args[1]
c := *cfg
switch key {
case "apikey":
c.APIKey = value
case "output":
c.Output = value
default:
return fmt.Errorf("unknown config key: %s (supported: apikey, output)", key)
}
if err := config.Save(cfgPath, c); err != nil {
return fmt.Errorf("save config: %w", err)
}
fmt.Printf("Set %s = %s\n", key, value)
return nil
},
}
configPathCmd := &cobra.Command{
Use: "path",
Short: "Print config file path",
Annotations: map[string]string{"skipSDK": "true"},
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println(cfgPath)
return nil
},
}
configCmd.AddCommand(configShowCmd)
configCmd.AddCommand(configSetCmd)
configCmd.AddCommand(configPathCmd)
return configCmd
}
+138
View File
@@ -0,0 +1,138 @@
package domain
import (
"fmt"
"strconv"
"github.com/XingfenD/rainyun_api_go_sdk/apis/common"
"github.com/XingfenD/rainyun_api_go_sdk/apis/domain"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output"
"github.com/XingfenD/rainyun_api_go_sdk/sdk"
"github.com/spf13/cobra"
)
func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command {
domainCmd := &cobra.Command{
Use: "domain",
Short: "Manage domains",
}
domainListCmd := &cobra.Command{
Use: "list",
Short: "List domains",
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := (*rySDK).GetDomainList(&domain.GetDomainListRequest{
Options: common.StandQueryParameters{Page: 1, PerPage: 50},
})
if err != nil {
return err
}
domains := make([]model.Domain, len(resp.Data.Records))
for i, r := range resp.Data.Records {
domains[i] = model.Domain{
ID: strconv.Itoa(r.ID),
Name: r.DomainName,
}
}
return (*out).Print(domains)
},
}
dnsCmd := &cobra.Command{
Use: "dns <domain-id>",
Short: "Manage DNS records",
Args: cobra.ExactArgs(1),
}
dnsListCmd := &cobra.Command{
Use: "list",
Short: "List DNS records",
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid domain id: %s", args[0])
}
resp, err := (*rySDK).GetDomainDNSRecordList(id)
if err != nil {
return err
}
records := make([]model.DNSRecord, len(resp.Data))
for i, r := range resp.Data {
records[i] = model.DNSRecord{
ID: strconv.Itoa(r.ID),
Type: r.RecordType,
Name: r.Host,
Value: r.Value,
TTL: r.TTL,
}
}
return (*out).Print(records)
},
}
dnsAddCmd := &cobra.Command{
Use: "add <domain-id>",
Short: "Add a DNS record",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid domain id: %s", args[0])
}
recType, _ := cmd.Flags().GetString("type")
host, _ := cmd.Flags().GetString("host")
value, _ := cmd.Flags().GetString("value")
ttl, _ := cmd.Flags().GetInt("ttl")
req := &domain.AddDomainDNSRecordRequest{
Type: recType,
Host: host,
Value: value,
TTL: ttl,
Line: "DEFAULT",
Level: 10,
}
if _, err := (*rySDK).AddDomainDNSRecord(id, req); err != nil {
return err
}
fmt.Printf("DNS record added for domain %s\n", args[0])
return nil
},
}
dnsDeleteCmd := &cobra.Command{
Use: "delete <domain-id> <record-id>",
Short: "Delete a DNS record",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
domainID, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid domain id: %s", args[0])
}
recordID, err := strconv.Atoi(args[1])
if err != nil {
return fmt.Errorf("invalid record id: %s", args[1])
}
if _, err := (*rySDK).DeleteDomainDNSRecord(domainID, recordID); err != nil {
return err
}
fmt.Printf("DNS record %s deleted for domain %s\n", args[1], args[0])
return nil
},
}
dnsAddCmd.Flags().String("type", "A", "Record type (A, AAAA, CNAME, TXT, MX, SRV)")
dnsAddCmd.Flags().String("host", "@", "Record host name")
dnsAddCmd.Flags().String("value", "", "Record value")
dnsAddCmd.Flags().Int("ttl", 600, "TTL in seconds")
dnsAddCmd.MarkFlagRequired("value")
dnsCmd.AddCommand(dnsListCmd)
dnsCmd.AddCommand(dnsAddCmd)
dnsCmd.AddCommand(dnsDeleteCmd)
domainCmd.AddCommand(domainListCmd)
domainCmd.AddCommand(dnsCmd)
return domainCmd
}
+205
View File
@@ -0,0 +1,205 @@
package server
import (
"fmt"
"strconv"
"time"
"github.com/XingfenD/rainyun_api_go_sdk/apis/common"
"github.com/XingfenD/rainyun_api_go_sdk/apis/rcs"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output"
"github.com/XingfenD/rainyun_api_go_sdk/sdk"
"github.com/spf13/cobra"
)
func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer, raw *bool) *cobra.Command {
serverCmd := &cobra.Command{
Use: "server",
Short: "Manage cloud servers",
}
serverListCmd := &cobra.Command{
Use: "list",
Short: "List servers",
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := (*rySDK).GetRcsList(&rcs.GetRcsListRequest{
Options: common.StandQueryParameters{Page: 1, PerPage: 50},
})
if err != nil {
return err
}
servers := make([]model.Server, len(resp.Data.Records))
for i, r := range resp.Data.Records {
servers[i] = toServer(r)
}
if *raw {
return (*out).Print(resp)
}
return (*out).Print(servers)
},
}
serverGetCmd := &cobra.Command{
Use: "get <id>",
Short: "Get server details",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
resp, err := (*rySDK).GetRcsDetail(id)
if err != nil {
return err
}
s := toServer(resp.Data.Data)
if *raw {
return (*out).Print(resp)
}
return (*out).Print(s)
},
}
serverStartCmd := &cobra.Command{
Use: "start <id>",
Short: "Start a server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
if _, err := (*rySDK).StartRcs(id); err != nil {
return err
}
fmt.Printf("Server %s started\n", args[0])
return nil
},
}
serverStopCmd := &cobra.Command{
Use: "stop <id>",
Short: "Stop a server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
if _, err := (*rySDK).StopRcs(id); err != nil {
return err
}
fmt.Printf("Server %s stopped\n", args[0])
return nil
},
}
serverRebootCmd := &cobra.Command{
Use: "reboot <id>",
Short: "Reboot a server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
if _, err := (*rySDK).RebootRcs(id); err != nil {
return err
}
fmt.Printf("Server %s rebooted\n", args[0])
return nil
},
}
serverReinstallCmd := &cobra.Command{
Use: "reinstall <id>",
Short: "Reinstall server OS",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
osID, _ := cmd.Flags().GetInt("os-id")
if osID == 0 {
return fmt.Errorf("--os-id flag is required (e.g. --os-id 123)")
}
if _, err := (*rySDK).ReinstallRcs(id, &rcs.ReinstallRcsRequest{OsID: osID}); err != nil {
return err
}
fmt.Printf("Server %s reinstalling with os-id %d\n", args[0], osID)
return nil
},
}
serverResetPasswordCmd := &cobra.Command{
Use: "reset-password <id>",
Short: "Reset server password",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
if _, err := (*rySDK).ResetRcsPassword(id, ""); err != nil {
return err
}
fmt.Printf("Password reset for server %s\n", args[0])
return nil
},
}
serverVNCCmd := &cobra.Command{
Use: "vnc <id>",
Short: "Get VNC connection URL",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid server id: %s", args[0])
}
v, err := (*rySDK).GetRcsVnc(id, "novnc")
if err != nil {
return err
}
url, uerr := common.GetVncConnectURL(v)
if uerr != nil || url == "" {
fmt.Println(v.Data.VNCProxyURL)
} else {
fmt.Println(url)
}
return nil
},
}
serverReinstallCmd.Flags().Int("os-id", 0, "OS template ID (required)")
serverReinstallCmd.MarkFlagRequired("os-id")
serverCmd.AddCommand(serverListCmd)
serverCmd.AddCommand(serverGetCmd)
serverCmd.AddCommand(serverStartCmd)
serverCmd.AddCommand(serverStopCmd)
serverCmd.AddCommand(serverRebootCmd)
serverCmd.AddCommand(serverReinstallCmd)
serverCmd.AddCommand(serverResetPasswordCmd)
serverCmd.AddCommand(serverVNCCmd)
return serverCmd
}
func toServer(r rcs.RcsRecord) model.Server {
return model.Server{
ID: strconv.Itoa(r.ID),
Name: r.HostName,
Status: r.Status,
IP: r.MainIPv4,
CPU: r.CPU,
Memory: r.Memory,
Disk: r.Disk,
OS: r.OsName,
Region: r.Zone,
ExpireAt: time.Unix(int64(r.ExpDate), 0),
}
}
+100
View File
@@ -0,0 +1,100 @@
package storage
import (
"fmt"
"strconv"
"github.com/XingfenD/rainyun_api_go_sdk/apis/common"
"github.com/XingfenD/rainyun_api_go_sdk/apis/ros"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/model"
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output"
"github.com/XingfenD/rainyun_api_go_sdk/sdk"
"github.com/spf13/cobra"
)
func Cmd(rySDK **sdk.RainyunSDK, out **output.Printer) *cobra.Command {
storageCmd := &cobra.Command{
Use: "storage",
Short: "Manage object storage",
}
storageListCmd := &cobra.Command{
Use: "list",
Short: "List storage instances",
RunE: func(cmd *cobra.Command, args []string) error {
resp, err := (*rySDK).GetRosInstanceList(&ros.GetRosInstanceListRequest{
Options: common.StandQueryParameters{Page: 1, PerPage: 50},
})
if err != nil {
return err
}
instances := make([]model.StorageInstance, len(resp.Data.Records))
for i, r := range resp.Data.Records {
instances[i] = model.StorageInstance{
ID: strconv.Itoa(r.ID),
Name: r.Tag,
Status: r.Status,
}
}
return (*out).Print(instances)
},
}
bucketCmd := &cobra.Command{
Use: "bucket <instance-id>",
Short: "Manage buckets",
Args: cobra.ExactArgs(1),
}
bucketListCmd := &cobra.Command{
Use: "list",
Short: "List buckets",
RunE: func(cmd *cobra.Command, args []string) error {
instanceID, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid instance id: %s", args[0])
}
resp, err := (*rySDK).GetRosBucketListByInstance(instanceID)
if err != nil {
return err
}
buckets := make([]model.Bucket, len(resp.Data.Records))
for i, b := range resp.Data.Records {
buckets[i] = model.Bucket{
ID: strconv.Itoa(b.ID),
Name: b.Name,
}
}
return (*out).Print(buckets)
},
}
bucketCreateCmd := &cobra.Command{
Use: "create <name>",
Short: "Create a bucket",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
instanceID, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid instance id: %s", args[0])
}
name := args[1]
if _, err := (*rySDK).CreateRosBucket(ros.CreateRosBucketRequest{
BucketName: name,
InstanceID: instanceID,
}); err != nil {
return err
}
fmt.Printf("Bucket %q created\n", name)
return nil
},
}
bucketCmd.AddCommand(bucketListCmd)
bucketCmd.AddCommand(bucketCreateCmd)
storageCmd.AddCommand(storageListCmd)
storageCmd.AddCommand(bucketCmd)
return storageCmd
}