package servers import ( "fmt" "os" "strings" "text/tabwriter" "github.com/spf13/cobra" "github.com/vandachevici/gconsole/api" ) // NewServersCmd returns the 'servers' command with all subcommands. func NewServersCmd(getAPIURL func(string) string) *cobra.Command { cmd := &cobra.Command{ Use: "servers", Short: "Manage game servers", } var apiURLFlag string cmd.PersistentFlags().StringVar(&apiURLFlag, "api-url", "", "API URL override") client := func() *api.Client { return api.NewClient(getAPIURL(apiURLFlag)) } cmd.AddCommand(newListCmd(client)) cmd.AddCommand(newGetCmd(client)) cmd.AddCommand(newLogsCmd(client)) cmd.AddCommand(newCreateCmd(client)) cmd.AddCommand(newEditCmd(client)) cmd.AddCommand(newDeleteCmd(client)) cmd.AddCommand(newStartCmd(client)) cmd.AddCommand(newStopCmd(client)) cmd.AddCommand(newRestartCmd(client)) return cmd } // --- list --- func newListCmd(client func() *api.Client) *cobra.Command { return &cobra.Command{ Use: "list", Short: "List all game servers", RunE: func(cmd *cobra.Command, args []string) error { servers, err := client().ListServers() if err != nil { return err } w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) fmt.Fprintln(w, "NAME\tTYPE\tSTATUS\tREADY\tNODEPORT\tIMAGE") for _, s := range servers { fmt.Fprintf(w, "%s\t%s\t%s\t%d/%d\t%d\t%s\n", s.Name, s.Type, s.Status, s.Ready, s.Replicas, s.NodePort, s.Image) } return w.Flush() }, } } // --- get --- func newGetCmd(client func() *api.Client) *cobra.Command { return &cobra.Command{ Use: "get ", Short: "Get details of a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { s, err := client().GetServer(args[0]) if err != nil { return err } fmt.Printf("Name: %s\n", s.Name) fmt.Printf("Type: %s\n", s.Type) fmt.Printf("Status: %s\n", s.Status) fmt.Printf("Ready: %d/%d\n", s.Ready, s.Replicas) fmt.Printf("Image: %s\n", s.Image) fmt.Printf("Port: %d\n", s.Port) fmt.Printf("NodePort: %d\n", s.NodePort) fmt.Printf("Created: %s\n", s.CreatedAt) if len(s.EnvVars) > 0 { fmt.Println("Env Vars:") for k, v := range s.EnvVars { fmt.Printf(" %s=%s\n", k, v) } } return nil }, } } // --- logs --- func newLogsCmd(client func() *api.Client) *cobra.Command { var lines int cmd := &cobra.Command{ Use: "logs ", Short: "Get logs from a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { logs, err := client().GetLogs(args[0], lines) if err != nil { return err } fmt.Print(logs) return nil }, } cmd.Flags().IntVar(&lines, "lines", 100, "Number of log lines to retrieve") return cmd } // --- create --- func newCreateCmd(client func() *api.Client) *cobra.Command { var ( image string port int32 nodePort int32 gameType string envVars []string ) cmd := &cobra.Command{ Use: "create ", Short: "Create a new game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { req := api.CreateServerRequest{ Name: args[0], Type: gameType, Image: image, Port: port, NodePort: nodePort, EnvVars: parseEnvVars(envVars), } s, err := client().CreateServer(req) if err != nil { return err } fmt.Printf("Created server %q (type: %s, image: %s)\n", s.Name, s.Type, s.Image) return nil }, } cmd.Flags().StringVar(&image, "image", "", "Container image (required)") cmd.Flags().Int32Var(&port, "port", 25565, "Game server port") cmd.Flags().Int32Var(&nodePort, "node-port", 0, "Kubernetes NodePort (0 = auto-assign)") cmd.Flags().StringVar(&gameType, "type", "unknown", "Server type (minecraft, factorio, openttd, ...)") cmd.Flags().StringArrayVar(&envVars, "env", nil, "Environment variables (KEY=VALUE), repeatable") cmd.MarkFlagRequired("image") return cmd } // --- edit --- func newEditCmd(client func() *api.Client) *cobra.Command { var ( image string envVars []string ) cmd := &cobra.Command{ Use: "edit ", Short: "Edit a game server's image or environment variables", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { req := api.UpdateServerRequest{ Image: image, EnvVars: parseEnvVars(envVars), } s, err := client().UpdateServer(args[0], req) if err != nil { return err } fmt.Printf("Updated server %q\n", s.Name) return nil }, } cmd.Flags().StringVar(&image, "image", "", "New container image") cmd.Flags().StringArrayVar(&envVars, "env", nil, "Environment variables (KEY=VALUE), repeatable") return cmd } // --- delete --- func newDeleteCmd(client func() *api.Client) *cobra.Command { var confirm bool cmd := &cobra.Command{ Use: "delete ", Short: "Delete a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if !confirm { return fmt.Errorf("pass --confirm to delete server %q", args[0]) } if err := client().DeleteServer(args[0]); err != nil { return err } fmt.Printf("Deleted server %q\n", args[0]) return nil }, } cmd.Flags().BoolVar(&confirm, "confirm", false, "Confirm deletion") return cmd } // --- start / stop / restart --- func newStartCmd(client func() *api.Client) *cobra.Command { return &cobra.Command{ Use: "start ", Short: "Start a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if err := client().Start(args[0]); err != nil { return err } fmt.Printf("Started %q\n", args[0]) return nil }, } } func newStopCmd(client func() *api.Client) *cobra.Command { return &cobra.Command{ Use: "stop ", Short: "Stop a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if err := client().Stop(args[0]); err != nil { return err } fmt.Printf("Stopped %q\n", args[0]) return nil }, } } func newRestartCmd(client func() *api.Client) *cobra.Command { return &cobra.Command{ Use: "restart ", Short: "Restart a game server", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if err := client().Restart(args[0]); err != nil { return err } fmt.Printf("Restarting %q\n", args[0]) return nil }, } } // parseEnvVars converts ["KEY=VAL", ...] to map[string]string. func parseEnvVars(pairs []string) map[string]string { if len(pairs) == 0 { return nil } m := make(map[string]string, len(pairs)) for _, p := range pairs { parts := strings.SplitN(p, "=", 2) if len(parts) == 2 { m[parts[0]] = parts[1] } } return m }