package cmd
import (
"context"
"flag"
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
"github.com/openeuler/Conch/internal/cli/client"
)
func printSandboxHelp(out io.Writer) {
fmt.Fprintln(out, "Usage:")
fmt.Fprintln(out, " conch sandbox <command> [options]")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Commands:")
fmt.Fprintln(out, " create Create a sandbox from a Template Name or the daemon default.")
fmt.Fprintln(out, " checkpoint Checkpoint a sandbox into a resumable template.")
fmt.Fprintln(out, " suspend Suspend a running sandbox.")
fmt.Fprintln(out, " resume Resume a suspended sandbox.")
fmt.Fprintln(out, " delete Delete a sandbox.")
fmt.Fprintln(out, " ls List sandboxes.")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Run 'conch sandbox <command> --help' for command-specific usage.")
}
func PrintSandboxCreateHelp(out io.Writer) {
fmt.Fprintln(out, "Usage:")
fmt.Fprintln(out, " conch sandbox create [--template-name <template-name> | --template-id <template-id>] [options]")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Description:")
fmt.Fprintln(out, " Create a sandbox from either a mutable Template Name or an immutable Template ID.")
fmt.Fprintln(out, " Unset template and resource fields use conchd sandbox.default_spec.")
fmt.Fprintln(out, "")
fmt.Fprintln(out, "Options:")
fmt.Fprintln(out, " --template-name string")
fmt.Fprintln(out, " Template Name (default: conchd sandbox.default_spec.template_name)")
fmt.Fprintln(out, " --template-id string")
fmt.Fprintln(out, " Template ID (default: conchd sandbox.default_spec.template_id)")
fmt.Fprintln(out, " --sandbox-id string")
fmt.Fprintln(out, " sandbox ID (default: generated)")
fmt.Fprintln(out, " --ram-mb int")
fmt.Fprintln(out, " memory size in MiB (default: conchd sandbox.default_spec.ram_mb)")
fmt.Fprintln(out, " --config string")
fmt.Fprintln(out, " config file path")
}
func RunSandbox(ctx context.Context, args []string) error {
if len(args) == 0 || args[0] == "-h" || args[0] == "--help" {
printSandboxHelp(os.Stdout)
return nil
}
switch args[0] {
case "create":
if len(args) >= 2 && (args[1] == "-h" || args[1] == "--help") {
PrintSandboxCreateHelp(os.Stdout)
return nil
}
return runSandboxCreate(ctx, args[1:])
case "checkpoint":
return runSandboxCheckpoint(ctx, args[1:])
case "suspend":
return runSandboxLifecycle(ctx, args[1:], "suspend")
case "resume":
return runSandboxLifecycle(ctx, args[1:], "resume")
case "delete":
return runSandboxDelete(ctx, args[1:])
case "ls", "list":
return runSandboxList(ctx, args[1:])
default:
printSandboxHelp(os.Stderr)
return fmt.Errorf("unknown sandbox command %q", args[0])
}
}
func runSandboxDelete(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("sandbox delete", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
configPath := fs.String("config", "", "config file path")
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 1 {
return fmt.Errorf("conch sandbox delete: exactly one sandbox ID is required")
}
c, err := client.New(client.Options{ConfigPath: *configPath})
if err != nil {
return fmt.Errorf("conch sandbox delete: create API client: %w", err)
}
id := fs.Arg(0)
if err := c.DeleteSandbox(ctx, id); err != nil {
return fmt.Errorf("conch sandbox delete: %w", err)
}
fmt.Fprintf(os.Stdout, "Deleted sandbox: %s\n", id)
return nil
}
func runSandboxList(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("sandbox ls", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
configPath := fs.String("config", "", "config file path")
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 0 {
return fmt.Errorf("conch sandbox ls: unexpected positional arguments: %v", fs.Args())
}
c, err := client.New(client.Options{ConfigPath: *configPath})
if err != nil {
return fmt.Errorf("conch sandbox ls: create API client: %w", err)
}
records, err := c.ListSandboxes(ctx)
if err != nil {
return fmt.Errorf("conch sandbox ls: %w", err)
}
return printSandboxList(os.Stdout, records)
}
func printSandboxList(out io.Writer, records []client.SandboxRecord) error {
sort.SliceStable(records, func(i, j int) bool {
return records[i].SandboxID < records[j].SandboxID
})
tw := tabwriter.NewWriter(out, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "ID\tTEMPLATE_NAME\tTEMPLATE_ID\tCPU\tMEMORY_MB\tSTARTED_AT")
for _, record := range records {
fmt.Fprintf(tw, "%s\t%s\t%s\t%d\t%d\t%s\n",
record.SandboxID, record.TemplateName, record.TemplateID,
record.CPUCount, record.MemoryMB, record.StartedAt)
}
return tw.Flush()
}
func runSandboxCreate(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("sandbox create", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
templateName := fs.String("template-name", "", "Template Name (uses daemon default if omitted)")
templateID := fs.String("template-id", "", "Template ID (uses daemon default if omitted)")
sandboxID := fs.String("sandbox-id", "", "sandbox ID")
configPath := fs.String("config", "", "config file path")
ramMB := fs.Int64("ram-mb", 0, "memory size in MiB")
fs.Usage = func() { PrintSandboxCreateHelp(os.Stderr) }
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 0 {
return fmt.Errorf("conch sandbox create: unexpected positional arguments: %v", fs.Args())
}
if *ramMB < 0 {
return fmt.Errorf("conch sandbox create: --ram-mb must not be negative")
}
if strings.TrimSpace(*templateName) != "" && strings.TrimSpace(*templateID) != "" {
return fmt.Errorf("conch sandbox create: --template-name and --template-id are mutually exclusive")
}
id := *sandboxID
if id == "" {
id = fmt.Sprintf("sandbox-%d", time.Now().UnixNano())
}
conchClient, err := client.New(client.Options{ConfigPath: *configPath})
if err != nil {
return fmt.Errorf("conch sandbox create: create API client: %w", err)
}
if _, err := conchClient.CreateSandbox(ctx, client.SandboxCreateRequest{
TemplateName: *templateName,
TemplateID: *templateID,
SandboxID: id,
RAMMB: *ramMB,
}); err != nil {
return fmt.Errorf("conch sandbox create: %w", err)
}
fmt.Fprintf(os.Stdout, "Sandbox: %s\n", id)
return nil
}
func runSandboxCheckpoint(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("sandbox checkpoint", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
configPath := fs.String("config", "", "config file path")
templateName := fs.String("template-name", "", "name to create or update with the checkpoint Template")
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 1 {
return fmt.Errorf("conch sandbox checkpoint: exactly one sandbox ID is required")
}
if *templateName == "" {
return fmt.Errorf("conch sandbox checkpoint: --template-name is required")
}
conchClient, err := client.New(client.Options{ConfigPath: *configPath})
if err != nil {
return fmt.Errorf("conch sandbox checkpoint: create API client: %w", err)
}
checkpoint, err := conchClient.CheckpointSandbox(ctx, client.SandboxCheckpointRequest{
SandboxID: fs.Arg(0),
TemplateName: *templateName,
})
if err != nil {
return fmt.Errorf("conch sandbox checkpoint: %w", err)
}
if checkpoint.Status != "ok" {
return fmt.Errorf("conch sandbox checkpoint: unexpected status %q", checkpoint.Status)
}
fmt.Fprintf(os.Stdout, "Template Name: %s\n", strings.TrimSpace(*templateName))
fmt.Fprintf(os.Stdout, "Template ID: %s\n", checkpoint.TemplateID)
return nil
}
func runSandboxLifecycle(ctx context.Context, args []string, op string) error {
fs := flag.NewFlagSet("sandbox "+op, flag.ContinueOnError)
fs.SetOutput(os.Stderr)
configPath := fs.String("config", "", "config file path")
if err := fs.Parse(args); err != nil {
return err
}
if fs.NArg() != 1 {
return fmt.Errorf("conch sandbox %s: exactly one sandbox ID is required", op)
}
c, err := client.New(client.Options{ConfigPath: *configPath})
if err != nil {
return fmt.Errorf("conch sandbox %s: create API client: %w", op, err)
}
id := fs.Arg(0)
req := client.SandboxLifecycleRequest{SandboxID: id}
switch op {
case "suspend":
err = c.SuspendSandbox(ctx, req)
case "resume":
err = c.ResumeSandbox(ctx, req)
}
if err != nil {
return fmt.Errorf("conch sandbox %s: %w", op, err)
}
fmt.Fprintf(os.Stdout, "%s sandbox: %s\n", op, id)
return nil
}