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:
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/billing"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/configcmd"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/domain"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/server"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/commands/storage"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/config"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/cmd/ry/internal/output"
|
||||||
|
"github.com/XingfenD/rainyun_api_go_sdk/sdk"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg *config.Config
|
||||||
|
cfgPath string
|
||||||
|
rySDK *sdk.RainyunSDK
|
||||||
|
out *output.Printer
|
||||||
|
flagAPIKey string
|
||||||
|
flagOutput string
|
||||||
|
flagRaw bool
|
||||||
|
flagVerbose bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "ry",
|
||||||
|
Short: "Rainyun CLI — manage cloud resources from your terminal",
|
||||||
|
Long: `ry is a CLI that lets you manage Rainyun cloud resources from your terminal.
|
||||||
|
|
||||||
|
Use "ry [command] --help" for more information about a command.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
var err error
|
||||||
|
cfg, err = config.Load(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("load config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Annotations != nil && cmd.Annotations["skipSDK"] == "true" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
apiKey := cfg.APIKey
|
||||||
|
if flagAPIKey != "" {
|
||||||
|
apiKey = flagAPIKey
|
||||||
|
}
|
||||||
|
if env := os.Getenv("RY_APIKEY"); env != "" {
|
||||||
|
apiKey = env
|
||||||
|
}
|
||||||
|
if apiKey == "" {
|
||||||
|
return fmt.Errorf("no api key configured — run 'ry config set apikey <key>'")
|
||||||
|
}
|
||||||
|
rySDK = sdk.New(apiKey)
|
||||||
|
|
||||||
|
outputFormat := cfg.Output
|
||||||
|
if flagOutput != "" {
|
||||||
|
outputFormat = flagOutput
|
||||||
|
}
|
||||||
|
if flagRaw {
|
||||||
|
outputFormat = "raw"
|
||||||
|
}
|
||||||
|
out = output.New(outputFormat, os.Stdout)
|
||||||
|
if flagVerbose {
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] config: %s\n", cfgPath)
|
||||||
|
fmt.Fprintf(os.Stderr, "[debug] output: %s\n", outputFormat)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cfgPath = config.DefaultPath()
|
||||||
|
rootCmd.PersistentFlags().StringVar(&flagAPIKey, "apikey", "", "Rainyun API key")
|
||||||
|
rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "", "Output format: table, json, yaml, raw")
|
||||||
|
rootCmd.PersistentFlags().BoolVar(&flagRaw, "raw", false, "Output raw API response")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&flagVerbose, "verbose", "v", false, "Enable verbose output")
|
||||||
|
|
||||||
|
completionCmd := &cobra.Command{
|
||||||
|
Use: "completion [bash|zsh|fish]",
|
||||||
|
Short: "Generate shell completion script",
|
||||||
|
Annotations: map[string]string{"skipSDK": "true"},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
switch args[0] {
|
||||||
|
case "bash":
|
||||||
|
return rootCmd.GenBashCompletion(os.Stdout)
|
||||||
|
case "zsh":
|
||||||
|
return rootCmd.GenZshCompletion(os.Stdout)
|
||||||
|
case "fish":
|
||||||
|
return rootCmd.GenFishCompletion(os.Stdout, true)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported shell: %s", args[0])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rootCmd.AddCommand(completionCmd)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(configcmd.Cmd(&cfg, cfgPath))
|
||||||
|
rootCmd.AddCommand(server.Cmd(&rySDK, &out, &flagRaw))
|
||||||
|
rootCmd.AddCommand(storage.Cmd(&rySDK, &out))
|
||||||
|
rootCmd.AddCommand(domain.Cmd(&rySDK, &out))
|
||||||
|
rootCmd.AddCommand(billing.Cmd(&rySDK, &out))
|
||||||
|
}
|
||||||
@@ -6,6 +6,25 @@ All notable changes to this template should be documented in this file.
|
|||||||
The format loosely follows Keep a Changelog and can be adapted to the team's habits.
|
The format loosely follows Keep a Changelog and can be adapted to the team's habits.
|
||||||
本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。
|
本文档参考了 Keep a Changelog 的思路,也可以根据团队习惯调整。
|
||||||
|
|
||||||
|
## [Unreleased] - 2026-08-13
|
||||||
|
|
||||||
|
### Added / 新增
|
||||||
|
|
||||||
|
- 迁移 rainyun-cli 到仓库内 `cmd/ry/`:新增 `main.go`、`root.go`、`commands/{server,domain,storage,billing,configcmd}`、`internal/{config,model,output}`。
|
||||||
|
- CLI 直接调用 `sdk.New(apiKey)`,删除 CLI 自带 provider/HTTP 层。
|
||||||
|
- 配置简化为单一 `apikey` + `output`(table/json/yaml),落盘到 `~/.config/ry/config.yaml`。
|
||||||
|
- 补齐 SDK 4 个缺口端点:`domain list`、`dns list`、`bucket-by-instance`、`expense orders`。
|
||||||
|
- 新增 `cmd/ry/internal/config` 与 `cmd/ry/internal/output` 测试(6/6 + 7/7)。
|
||||||
|
|
||||||
|
### Fixed / 修复
|
||||||
|
|
||||||
|
- 修复 `output --raw` 输出 bug:新增 raw case,原样透传 SDK Response。
|
||||||
|
|
||||||
|
### Changed / 变更
|
||||||
|
|
||||||
|
- 命令 ID 参数统一为 `int`,显示层 model ID 保持 `string`。
|
||||||
|
- `server reinstall` flag 从 `--os` 改为 `--os-id int`。
|
||||||
|
|
||||||
## [0.2.0] - 2026-08-05
|
## [0.2.0] - 2026-08-05
|
||||||
|
|
||||||
### Added / 新增
|
### Added / 新增
|
||||||
|
|||||||
Reference in New Issue
Block a user