refactor: move cmd handlers to cmd/cli/commands/ following tag_file architecture

This commit is contained in:
2026-07-28 18:41:12 +08:00
parent 9c3eba2327
commit 71a842a327
15 changed files with 586 additions and 562 deletions
+37
View File
@@ -0,0 +1,37 @@
package billing
import (
"fmt"
"git.yoresee.cc/nightstar/ry/internal/output"
"git.yoresee.cc/nightstar/ry/internal/provider"
"github.com/spf13/cobra"
)
func Cmd(prov *provider.Provider, 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 {
svc := (*prov).Billing()
if svc == nil {
return fmt.Errorf("billing not supported by current provider")
}
orders, err := svc.Orders(cmd.Context())
if err != nil {
return err
}
return (*out).Print(orders)
},
}
billingCmd.AddCommand(ordersCmd)
return billingCmd
}
+75
View File
@@ -0,0 +1,75 @@
package configcmd
import (
"fmt"
"strings"
"git.yoresee.cc/nightstar/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",
}
configShowCmd := &cobra.Command{
Use: "show",
Short: "Show current configuration",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("Config file: %s\n", cfgPath)
fmt.Printf("Current provider: %s\n", cfg.CurrentProvider)
fmt.Printf("Output format: %s\n", cfg.Output)
fmt.Println("Providers:")
for name, pc := range cfg.Providers {
masked := pc.APIKey
if len(masked) > 4 {
masked = masked[:2] + strings.Repeat("*", len(masked)-4) + masked[len(masked)-2:]
}
marker := " "
if name == cfg.CurrentProvider {
marker = "*"
}
fmt.Printf(" %s %s: api_key=%s\n", marker, name, masked)
}
return nil
},
}
configSetCmd := &cobra.Command{
Use: "set <key> <value>",
Short: "Set a config value",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
key, value := args[0], args[1]
switch key {
case "output":
cfg.Output = value
default:
return fmt.Errorf("unknown config key: %s", key)
}
if err := config.Save(cfgPath, cfg); 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",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println(cfgPath)
return nil
},
}
configCmd.AddCommand(configShowCmd)
configCmd.AddCommand(configSetCmd)
configCmd.AddCommand(configPathCmd)
return configCmd
}
+99
View File
@@ -0,0 +1,99 @@
package domain
import (
"fmt"
"git.yoresee.cc/nightstar/ry/internal/output"
"git.yoresee.cc/nightstar/ry/internal/provider"
"github.com/spf13/cobra"
)
func Cmd(prov *provider.Provider, 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 {
svc := (*prov).Domain()
if svc == nil {
return fmt.Errorf("domain management not supported by current provider")
}
domains, err := svc.List(cmd.Context())
if err != nil {
return err
}
return (*out).Print(domains)
},
}
dnsCmd := &cobra.Command{
Use: "dns <domain>",
Short: "Manage DNS records",
Args: cobra.MinimumNArgs(1),
}
dnsListCmd := &cobra.Command{
Use: "list",
Short: "List DNS records",
RunE: func(cmd *cobra.Command, args []string) error {
svc := (*prov).Domain()
if svc == nil {
return fmt.Errorf("domain management not supported")
}
records, err := svc.DNSList(cmd.Context(), args[0])
if err != nil {
return err
}
return (*out).Print(records)
},
}
dnsAddCmd := &cobra.Command{
Use: "add",
Short: "Add a DNS record",
RunE: func(cmd *cobra.Command, args []string) error {
svc := (*prov).Domain()
if svc == nil {
return fmt.Errorf("domain management not supported")
}
recType, _ := cmd.Flags().GetString("type")
name, _ := cmd.Flags().GetString("name")
value, _ := cmd.Flags().GetString("value")
ttl, _ := cmd.Flags().GetInt("ttl")
return svc.DNSAdd(cmd.Context(), args[0],
provider.DNSRecord{Type: recType, Name: name, Value: value, TTL: ttl})
},
}
dnsDeleteCmd := &cobra.Command{
Use: "delete <record-id>",
Short: "Delete a DNS record",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
svc := (*prov).Domain()
if svc == nil {
return fmt.Errorf("domain management not supported")
}
return svc.DNSDelete(cmd.Context(), args[0], args[1])
},
}
dnsAddCmd.Flags().String("type", "A", "Record type (A, CNAME, TXT)")
dnsAddCmd.Flags().String("name", "@", "Record 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
}
+94
View File
@@ -0,0 +1,94 @@
package providercmd
import (
"fmt"
"git.yoresee.cc/nightstar/ry/internal/config"
"git.yoresee.cc/nightstar/ry/internal/provider"
"github.com/spf13/cobra"
)
func Cmd(cfg *config.Config, cfgPath string) *cobra.Command {
providerCmd := &cobra.Command{
Use: "provider",
Short: "Manage cloud providers",
}
providerListCmd := &cobra.Command{
Use: "list",
Short: "List configured providers",
Annotations: map[string]string{"skipProvider": "true"},
RunE: func(cmd *cobra.Command, args []string) error {
current := ""
if cfg != nil {
current = cfg.CurrentProvider
}
for _, name := range provider.List() {
marker := " "
if name == current {
marker = "*"
}
fmt.Printf("%s %s\n", marker, name)
}
return nil
},
}
providerUseCmd := &cobra.Command{
Use: "use <name>",
Short: "Switch the current cloud provider",
Annotations: map[string]string{"skipProvider": "true"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
if _, ok := cfg.Providers[name]; !ok {
return fmt.Errorf("provider %q not configured — run 'ry provider add %s' first", name, name)
}
cfg.CurrentProvider = name
if err := config.Save(cfgPath, cfg); err != nil {
return fmt.Errorf("save config: %w", err)
}
fmt.Printf("Switched to provider %q\n", name)
return nil
},
}
providerAddCmd := &cobra.Command{
Use: "add <name>",
Short: "Add a new cloud provider",
Annotations: map[string]string{"skipProvider": "true"},
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
fmt.Printf("Adding provider %q...\n", name)
fmt.Print("Enter API Key: ")
var apiKey string
fmt.Scanln(&apiKey)
if apiKey == "" {
return fmt.Errorf("API Key is required")
}
if cfg.Providers == nil {
cfg.Providers = make(map[string]config.ProviderConfig)
}
cfg.Providers[name] = config.ProviderConfig{APIKey: apiKey}
if cfg.CurrentProvider == "" {
cfg.CurrentProvider = name
}
if err := config.Save(cfgPath, cfg); err != nil {
return fmt.Errorf("save config: %w", err)
}
fmt.Printf("Provider %q added successfully\n", name)
if cfg.CurrentProvider == name {
fmt.Printf("Set as current provider\n")
}
return nil
},
}
providerCmd.AddCommand(providerListCmd)
providerCmd.AddCommand(providerUseCmd)
providerCmd.AddCommand(providerAddCmd)
return providerCmd
}
+181
View File
@@ -0,0 +1,181 @@
package server
import (
"fmt"
"git.yoresee.cc/nightstar/ry/internal/output"
"git.yoresee.cc/nightstar/ry/internal/provider"
"github.com/spf13/cobra"
)
func Cmd(prov *provider.Provider, 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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported by current provider")
}
servers, err := svc.List(cmd.Context())
if err != nil {
return err
}
if *raw {
var r []any
for _, s := range servers {
r = append(r, s.Raw)
}
return (*out).Print(r)
}
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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
s, err := svc.Get(cmd.Context(), args[0])
if err != nil {
return err
}
if *raw {
return (*out).Print(s.Raw)
}
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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
if err := svc.Start(cmd.Context(), args[0]); 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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
if err := svc.Stop(cmd.Context(), args[0]); 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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
if err := svc.Reboot(cmd.Context(), args[0]); 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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
osTemplate, _ := cmd.Flags().GetString("os")
if osTemplate == "" {
return fmt.Errorf("--os flag is required (e.g. --os ubuntu-22.04)")
}
if err := svc.Reinstall(cmd.Context(), args[0], osTemplate); err != nil {
return err
}
fmt.Printf("Server %s reinstalling with %s\n", args[0], osTemplate)
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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
if err := svc.ResetPassword(cmd.Context(), args[0]); 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 {
svc := (*prov).Server()
if svc == nil {
return fmt.Errorf("server management not supported")
}
url, err := svc.GetVNC(cmd.Context(), args[0])
if err != nil {
return err
}
fmt.Println(url)
return nil
},
}
serverReinstallCmd.Flags().String("os", "", "OS template name (required)")
serverReinstallCmd.MarkFlagRequired("os")
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
}
+79
View File
@@ -0,0 +1,79 @@
package storage
import (
"fmt"
"git.yoresee.cc/nightstar/ry/internal/output"
"git.yoresee.cc/nightstar/ry/internal/provider"
"github.com/spf13/cobra"
)
func Cmd(prov *provider.Provider, 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 {
svc := (*prov).Storage()
if svc == nil {
return fmt.Errorf("storage management not supported by current provider")
}
instances, err := svc.List(cmd.Context())
if err != nil {
return err
}
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 {
svc := (*prov).Storage()
if svc == nil {
return fmt.Errorf("storage management not supported")
}
buckets, err := svc.BucketList(cmd.Context(), args[0])
if err != nil {
return err
}
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 {
svc := (*prov).Storage()
if svc == nil {
return fmt.Errorf("storage management not supported")
}
if err := svc.BucketCreate(cmd.Context(), args[0], args[1]); err != nil {
return err
}
fmt.Printf("Bucket %q created\n", args[1])
return nil
},
}
bucketCmd.AddCommand(bucketListCmd)
bucketCmd.AddCommand(bucketCreateCmd)
storageCmd.AddCommand(storageListCmd)
storageCmd.AddCommand(bucketCmd)
return storageCmd
}
+1 -2
View File
@@ -4,12 +4,11 @@ import (
"fmt"
"os"
"git.yoresee.cc/nightstar/ry/internal/cmd"
_ "git.yoresee.cc/nightstar/ry/internal/provider/rainyun"
)
func main() {
if err := cmd.Execute(); err != nil {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
+120
View File
@@ -0,0 +1,120 @@
package main
import (
"fmt"
"os"
"git.yoresee.cc/nightstar/ry/internal/config"
"git.yoresee.cc/nightstar/ry/internal/output"
"git.yoresee.cc/nightstar/ry/internal/provider"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/billing"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/configcmd"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/domain"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/providercmd"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/server"
"git.yoresee.cc/nightstar/ry/cmd/cli/commands/storage"
"github.com/spf13/cobra"
)
var (
cfg *config.Config
cfgPath string
prov provider.Provider
out *output.Printer
flagProvider string
flagOutput string
flagRaw bool
flagDebug bool
)
var rootCmd = &cobra.Command{
Use: "ry",
Short: "Multi-cloud CLI — manage cloud resources from your terminal",
Long: `ry is a multi-cloud CLI that lets you manage 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["skipProvider"] == "true" {
return nil
}
providerName := cfg.CurrentProvider
if flagProvider != "" {
providerName = flagProvider
}
if env := os.Getenv("RY_PROVIDER"); env != "" {
providerName = env
}
if providerName == "" {
return fmt.Errorf("no provider configured — run 'ry provider add <name>' first")
}
pc := cfg.Providers[providerName]
prov, err = provider.New(providerName, provider.ProviderConfig{
APIKey: pc.APIKey,
AccessKeyID: pc.AccessKeyID,
AccessKeySecret: pc.AccessKeySecret,
Endpoint: pc.Endpoint,
})
if err != nil {
return err
}
outputFormat := cfg.Output
if flagOutput != "" {
outputFormat = flagOutput
}
if flagRaw {
outputFormat = "raw"
}
out = output.New(outputFormat, os.Stdout)
return nil
},
}
func init() {
cfgPath = config.DefaultPath()
rootCmd.PersistentFlags().StringVarP(&flagProvider, "provider", "p", "", "Cloud provider to use")
rootCmd.PersistentFlags().StringVarP(&flagOutput, "output", "o", "", "Output format: table, json, yaml, raw")
rootCmd.PersistentFlags().BoolVar(&flagRaw, "raw", false, "Output raw API response")
rootCmd.PersistentFlags().BoolVar(&flagDebug, "debug", false, "Enable debug output")
completionCmd := &cobra.Command{
Use: "completion [bash|zsh|fish]",
Short: "Generate shell completion script",
Annotations: map[string]string{"skipProvider": "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(providercmd.Cmd(cfg, cfgPath))
rootCmd.AddCommand(server.Cmd(&prov, &out, &flagRaw))
rootCmd.AddCommand(storage.Cmd(&prov, &out))
rootCmd.AddCommand(domain.Cmd(&prov, &out))
rootCmd.AddCommand(billing.Cmd(&prov, &out))
}