feat: domain/storage/billing/config cmds + rainyun domain/storage/billing services
This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var billingCmd = &cobra.Command{
|
||||||
|
Use: "billing",
|
||||||
|
Short: "Billing and orders",
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
billingCmd.AddCommand(ordersCmd)
|
||||||
|
rootCmd.AddCommand(billingCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.yoresee.cc/nightstar/ry/internal/config"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var configCmd = &cobra.Command{
|
||||||
|
Use: "config",
|
||||||
|
Short: "Manage ry configuration",
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var configPathCmd = &cobra.Command{
|
||||||
|
Use: "path",
|
||||||
|
Short: "Print config file path",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
fmt.Println(cfgPath)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
configCmd.AddCommand(configShowCmd)
|
||||||
|
configCmd.AddCommand(configSetCmd)
|
||||||
|
configCmd.AddCommand(configPathCmd)
|
||||||
|
rootCmd.AddCommand(configCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.yoresee.cc/nightstar/ry/internal/provider"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var domainCmd = &cobra.Command{
|
||||||
|
Use: "domain",
|
||||||
|
Short: "Manage domains",
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var dnsCmd = &cobra.Command{
|
||||||
|
Use: "dns <domain>",
|
||||||
|
Short: "Manage DNS records",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
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)
|
||||||
|
rootCmd.AddCommand(domainCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var storageCmd = &cobra.Command{
|
||||||
|
Use: "storage",
|
||||||
|
Short: "Manage object storage",
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var bucketCmd = &cobra.Command{
|
||||||
|
Use: "bucket <instance-id>",
|
||||||
|
Short: "Manage buckets",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
bucketCmd.AddCommand(bucketListCmd)
|
||||||
|
bucketCmd.AddCommand(bucketCreateCmd)
|
||||||
|
storageCmd.AddCommand(storageListCmd)
|
||||||
|
storageCmd.AddCommand(bucketCmd)
|
||||||
|
rootCmd.AddCommand(storageCmd)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.yoresee.cc/nightstar/ry/internal/provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BillingService struct {
|
||||||
|
client *client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BillingService) Orders(ctx context.Context) ([]provider.Order, error) {
|
||||||
|
type rainyunOrder struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
ProductName string `json:"product_name"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
CreateDate string `json:"create_date"`
|
||||||
|
}
|
||||||
|
var raw []rainyunOrder
|
||||||
|
if err := b.client.get(ctx, "/expense/orders/list", &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
orders := make([]provider.Order, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
t, _ := time.Parse("2006-01-02 15:04:05", r.CreateDate)
|
||||||
|
orders[i] = provider.Order{
|
||||||
|
ID: fmt.Sprintf("%d", r.ID),
|
||||||
|
Product: r.ProductName,
|
||||||
|
Amount: r.Amount,
|
||||||
|
Status: r.Status,
|
||||||
|
Created: t,
|
||||||
|
Raw: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orders, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.yoresee.cc/nightstar/ry/internal/provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DomainService struct {
|
||||||
|
client *client
|
||||||
|
}
|
||||||
|
|
||||||
|
type rainyunDomain struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
DomainName string `json:"domain_name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ExpireDate string `json:"expire_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DomainService) List(ctx context.Context) ([]provider.Domain, error) {
|
||||||
|
var raw []rainyunDomain
|
||||||
|
if err := d.client.get(ctx, "/product/domain/", &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
domains := make([]provider.Domain, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
domains[i] = provider.Domain{
|
||||||
|
ID: fmt.Sprintf("%d", r.ID),
|
||||||
|
Name: r.DomainName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DomainService) DNSList(ctx context.Context, domain string) ([]provider.DNSRecord, error) {
|
||||||
|
type rainyunDNSRecord struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
RecordType string `json:"record_type"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
TTL int `json:"ttl"`
|
||||||
|
}
|
||||||
|
var raw []rainyunDNSRecord
|
||||||
|
path := fmt.Sprintf("/product/domain/%s/dns/", domain)
|
||||||
|
if err := d.client.get(ctx, path, &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
records := make([]provider.DNSRecord, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
records[i] = provider.DNSRecord{
|
||||||
|
ID: fmt.Sprintf("%d", r.ID),
|
||||||
|
Type: r.RecordType,
|
||||||
|
Name: r.Host,
|
||||||
|
Value: r.Value,
|
||||||
|
TTL: r.TTL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DomainService) DNSAdd(ctx context.Context, domain string, record provider.DNSRecord) error {
|
||||||
|
body := map[string]any{
|
||||||
|
"record_type": record.Type,
|
||||||
|
"host": record.Name,
|
||||||
|
"value": record.Value,
|
||||||
|
"ttl": record.TTL,
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("/product/domain/%s/dns", domain)
|
||||||
|
return d.client.post(ctx, path, body, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DomainService) DNSDelete(ctx context.Context, domain string, recordID string) error {
|
||||||
|
path := fmt.Sprintf("/product/domain/%s/dns/%s", domain, recordID)
|
||||||
|
return d.client.delete(ctx, path, nil)
|
||||||
|
}
|
||||||
@@ -12,8 +12,11 @@ func init() {
|
|||||||
|
|
||||||
// Rainyun provider — 雨云 cloud platform.
|
// Rainyun provider — 雨云 cloud platform.
|
||||||
type Rainyun struct {
|
type Rainyun struct {
|
||||||
client *client
|
client *client
|
||||||
server *ServerService
|
server *ServerService
|
||||||
|
domain *DomainService
|
||||||
|
storage *StorageService
|
||||||
|
billing *BillingService
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a Rainyun provider from config.
|
// New creates a Rainyun provider from config.
|
||||||
@@ -21,6 +24,9 @@ func New(cfg provider.ProviderConfig) (provider.Provider, error) {
|
|||||||
c := newClient(cfg.Endpoint, cfg.APIKey)
|
c := newClient(cfg.Endpoint, cfg.APIKey)
|
||||||
r := &Rainyun{client: c}
|
r := &Rainyun{client: c}
|
||||||
r.server = &ServerService{client: c}
|
r.server = &ServerService{client: c}
|
||||||
|
r.domain = &DomainService{client: c}
|
||||||
|
r.storage = &StorageService{client: c}
|
||||||
|
r.billing = &BillingService{client: c}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +45,7 @@ func (r *Rainyun) Server() provider.ServerService {
|
|||||||
}
|
}
|
||||||
return r.server
|
return r.server
|
||||||
}
|
}
|
||||||
func (r *Rainyun) Domain() provider.DomainService { return nil }
|
func (r *Rainyun) Domain() provider.DomainService { return r.domain }
|
||||||
func (r *Rainyun) Storage() provider.StorageService { return nil }
|
func (r *Rainyun) Storage() provider.StorageService { return r.storage }
|
||||||
func (r *Rainyun) CDN() provider.CDNService { return nil }
|
func (r *Rainyun) CDN() provider.CDNService { return nil }
|
||||||
func (r *Rainyun) Billing() provider.BillingService { return nil }
|
func (r *Rainyun) Billing() provider.BillingService { return r.billing }
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package rainyun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.yoresee.cc/nightstar/ry/internal/provider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StorageService struct {
|
||||||
|
client *client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) List(ctx context.Context) ([]provider.StorageInstance, error) {
|
||||||
|
var raw []struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
if err := s.client.get(ctx, "/product/ros/instance", &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
instances := make([]provider.StorageInstance, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
instances[i] = provider.StorageInstance{
|
||||||
|
ID: fmt.Sprintf("%d", r.ID),
|
||||||
|
Name: r.Name,
|
||||||
|
Raw: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instances, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) BucketList(ctx context.Context, instanceID string) ([]provider.Bucket, error) {
|
||||||
|
type rainyunBucket struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
}
|
||||||
|
var raw []rainyunBucket
|
||||||
|
path := fmt.Sprintf("/product/ros/bucket/%s", instanceID)
|
||||||
|
if err := s.client.get(ctx, path, &raw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buckets := make([]provider.Bucket, len(raw))
|
||||||
|
for i, r := range raw {
|
||||||
|
buckets[i] = provider.Bucket{
|
||||||
|
ID: fmt.Sprintf("%d", r.ID),
|
||||||
|
Name: r.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buckets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StorageService) BucketCreate(ctx context.Context, instanceID, name string) error {
|
||||||
|
body := map[string]string{"name": name}
|
||||||
|
path := fmt.Sprintf("/product/ros/bucket/%s", instanceID)
|
||||||
|
return s.client.post(ctx, path, body, nil)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user