package cmd import ( "bufio" "errors" "fmt" "io" "os" "sort" "strconv" "strings" "github.com/rancher/cli/config" "github.com/sirupsen/logrus" "github.com/urfave/cli" "golang.org/x/exp/maps" ) type serverData struct { Index int Current string Name string URL string } // ServerCommand defines the 'rancher server' sub-commands func ServerCommand() cli.Command { cfg := &config.Config{} return cli.Command{ Name: "server", Usage: "Operations for the server", Description: `Switch or view the server currently in focus. `, Before: loadAndValidateConfig(cfg), Subcommands: []cli.Command{ { Name: "current", Usage: "Display the current server", Action: func(ctx *cli.Context) error { return serverCurrent(ctx.App.Writer, cfg) }, }, { Name: "delete", Usage: "Delete a server from the local config", ArgsUsage: "[SERVER_NAME]", Description: ` The server arg is optional, if not passed in a list of available servers will be displayed and one can be selected. `, Action: func(ctx *cli.Context) error { serverName, err := getSelectedServer(ctx, cfg) if err != nil { return err } return serverDelete(cfg, serverName) }, }, { Name: "ls", Usage: "List all servers", ArgsUsage: "None", Action: func(ctx *cli.Context) error { format := ctx.String("format") return serverLs(ctx.App.Writer, cfg, format) }, Flags: []cli.Flag{ cli.StringFlag{ Name: "format", Usage: "'json', 'yaml' or Custom format: '{{.Name}} {{.URL}}'", }, }, }, { Name: "switch", Usage: "Switch to a new server", ArgsUsage: "[SERVER_NAME]", Description: ` The server arg is optional, if not passed in a list of available servers will be displayed and one can be selected. `, Action: func(ctx *cli.Context) error { serverName, err := getSelectedServer(ctx, cfg) if err != nil { return err } return serverSwitch(cfg, serverName) }, }, }, } } // serverCurrent command to display the name of the current server in the local config func serverCurrent(out io.Writer, cfg *config.Config) error { serverName := cfg.CurrentServer currentServer, found := cfg.Servers[serverName] if !found { return errors.New("current server not set") } fmt.Fprintf(out, "Name: %s URL: %s\n", serverName, currentServer.URL) return nil } // serverDelete command to delete a server from the local config func serverDelete(cfg *config.Config, serverName string) error { _, ok := cfg.Servers[serverName] if !ok { return errors.New("server not found") } delete(cfg.Servers, serverName) if cfg.CurrentServer == serverName { cfg.CurrentServer = "" } err := cfg.Write() if err != nil { return err } logrus.Infof("Server %s deleted", serverName) return nil } // serverLs command to list rancher servers from the local config func serverLs(out io.Writer, cfg *config.Config, format string) error { writerConfig := &TableWriterConfig{ Writer: out, Format: format, } writer := NewTableWriterWithConfig([][]string{ {"CURRENT", "Current"}, {"NAME", "Name"}, {"URL", "URL"}, }, writerConfig) defer writer.Close() servers := getServers(cfg) for _, server := range servers { writer.Write(server) } return writer.Err() } // serverSwitch will alter and write the config to switch rancher server. func serverSwitch(cf *config.Config, serverName string) error { _, ok := cf.Servers[serverName] if !ok { return errors.New("server not found") } if len(cf.Servers[serverName].Project) == 0 { logrus.Warn("No context set; some commands will not work. Run 'rancher context switch'") } cf.CurrentServer = serverName err := cf.Write() if err != nil { return err } return nil } // getSelectedServer will get the selected server if provided as argument, // or it will prompt the user to select one. func getSelectedServer(ctx *cli.Context, cfg *config.Config) (string, error) { serverName := ctx.Args().First() if serverName != "" { return serverName, nil } return serverFromInput(ctx, cfg) } // serverFromInput displays the list of servers from the local config and // prompt the user to select one. func serverFromInput(ctx *cli.Context, cf *config.Config) (string, error) { servers := getServers(cf) if err := displayListServers(ctx, servers); err != nil { return "", err } fmt.Print("Select a Server:") reader := bufio.NewReader(os.Stdin) errMessage := fmt.Sprintf("Invalid input, enter a number between 1 and %v: ", len(servers)) var selection int for { input, err := reader.ReadString('\n') if err != nil { return "", err } input = strings.TrimSpace(input) if input != "" { i, err := strconv.Atoi(input) if err != nil { fmt.Print(errMessage) continue } if i <= len(servers) && i != 0 { selection = i - 1 break } fmt.Print(errMessage) continue } } return servers[selection].Name, nil } // displayListServers displays the list of rancher servers func displayListServers(ctx *cli.Context, servers []*serverData) error { writer := NewTableWriter([][]string{ {"INDEX", "Index"}, {"NAME", "Name"}, {"URL", "URL"}, }, ctx) defer writer.Close() for _, server := range servers { writer.Write(server) } return writer.Err() } // getServers returns an ordered slice (by name) of serverData func getServers(cfg *config.Config) []*serverData { serverNames := maps.Keys(cfg.Servers) sort.Strings(serverNames) servers := []*serverData{} for i, server := range serverNames { var current string if server == cfg.CurrentServer { current = "*" } servers = append(servers, &serverData{ Index: i + 1, Name: server, Current: current, URL: cfg.Servers[server].URL, }) } return servers } func loadAndValidateConfig(cfg *config.Config) cli.BeforeFunc { return func(ctx *cli.Context) error { conf, err := loadConfig(ctx) if err != nil { return err } *cfg = conf if len(cfg.Servers) == 0 { return errors.New("no servers are currently configured") } return nil } }