diff --git a/cmd/uncloud/context/ls.go b/cmd/uncloud/context/ls.go new file mode 100644 index 00000000..81b7ecc8 --- /dev/null +++ b/cmd/uncloud/context/ls.go @@ -0,0 +1,61 @@ +package context + +import ( + "fmt" + "maps" + "os" + "slices" + "text/tabwriter" + + "github.com/psviderski/uncloud/internal/cli" + "github.com/spf13/cobra" +) + +func NewListCommand() *cobra.Command { + var clusterContext string + + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List available cluster contexts.", + RunE: func(cmd *cobra.Command, args []string) error { + uncli := cmd.Context().Value("cli").(*cli.CLI) + return list(uncli) + }, + } + + cmd.Flags().StringVarP( + &clusterContext, "context", "c", "", + "Name of the cluster context. (default is the current context)", + ) + + return cmd +} + +func list(uncli *cli.CLI) error { + if uncli.Config == nil { + return fmt.Errorf("context management is not available: Uncloud configuration file is not being used") + } + + if len(uncli.Config.Contexts) == 0 { + fmt.Println("No contexts found") + return nil + } + + contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts)) + currentContext := uncli.Config.CurrentContext + + tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprintln(tw, "NAME\tCURRENT\tCONNECTIONS") + + for _, name := range contextNames { + current := "" + if name == currentContext { + current = "*" + } + connCount := len(uncli.Config.Contexts[name].Connections) + fmt.Fprintf(tw, "%s\t%s\t%d\n", name, current, connCount) + } + + return tw.Flush() +} diff --git a/cmd/uncloud/context/root.go b/cmd/uncloud/context/root.go new file mode 100644 index 00000000..25cba7bc --- /dev/null +++ b/cmd/uncloud/context/root.go @@ -0,0 +1,23 @@ +package context + +import ( + "github.com/spf13/cobra" +) + +func NewRootCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "context", + Aliases: []string{"ctx"}, + Short: "Switch between different cluster contexts. Contains subcommands to manage contexts.", + RunE: func(cmd *cobra.Command, args []string) error { + // TODO + return nil + }, + } + + cmd.AddCommand( + NewListCommand(), + ) + + return cmd +} diff --git a/cmd/uncloud/machine/list.go b/cmd/uncloud/machine/ls.go similarity index 100% rename from cmd/uncloud/machine/list.go rename to cmd/uncloud/machine/ls.go diff --git a/cmd/uncloud/main.go b/cmd/uncloud/main.go index 8cf0c40e..c2f3c21b 100644 --- a/cmd/uncloud/main.go +++ b/cmd/uncloud/main.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/psviderski/uncloud/cmd/uncloud/caddy" + cmdcontext "github.com/psviderski/uncloud/cmd/uncloud/context" "github.com/psviderski/uncloud/cmd/uncloud/dns" "github.com/psviderski/uncloud/cmd/uncloud/machine" "github.com/psviderski/uncloud/cmd/uncloud/service" @@ -67,10 +68,12 @@ func main() { cmd.PersistentFlags().StringVar(&opts.configPath, "uncloud-config", "~/.config/uncloud/config.yaml", "Path to the Uncloud configuration file.") _ = cmd.MarkPersistentFlagFilename("uncloud-config", "yaml", "yml") + // TODO: make --context a global flag and pass it as a value of the command context. cmd.AddCommand( NewDeployCommand(), caddy.NewRootCommand(), + cmdcontext.NewRootCommand(), dns.NewRootCommand(), machine.NewRootCommand(), service.NewRootCommand(), diff --git a/cmd/uncloud/service/list.go b/cmd/uncloud/service/ls.go similarity index 100% rename from cmd/uncloud/service/list.go rename to cmd/uncloud/service/ls.go diff --git a/cmd/uncloud/service/root.go b/cmd/uncloud/service/root.go index bf694356..e159e672 100644 --- a/cmd/uncloud/service/root.go +++ b/cmd/uncloud/service/root.go @@ -6,8 +6,9 @@ import ( func NewRootCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "service", - Short: "Manage services in an Uncloud cluster.", + Use: "service", + Aliases: []string{"svc"}, + Short: "Manage services in an Uncloud cluster.", } cmd.AddCommand( NewInspectCommand(), diff --git a/internal/cli/cli.go b/internal/cli/cli.go index cf720a53..c91dafc6 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -24,7 +24,7 @@ import ( const defaultContextName = "default" type CLI struct { - config *config.Config + Config *config.Config conn *config.MachineConnection } @@ -42,26 +42,26 @@ func New(configPath string, conn *config.MachineConnection) (*CLI, error) { } return &CLI{ - config: cfg, + Config: cfg, }, nil } func (cli *CLI) CreateContext(name string) error { - if _, ok := cli.config.Contexts[name]; ok { + if _, ok := cli.Config.Contexts[name]; ok { return fmt.Errorf("context '%s' already exists", name) } - cli.config.Contexts[name] = &config.Context{ + cli.Config.Contexts[name] = &config.Context{ Name: name, } - return cli.config.Save() + return cli.Config.Save() } func (cli *CLI) SetCurrentContext(name string) error { - if _, ok := cli.config.Contexts[name]; !ok { + if _, ok := cli.Config.Contexts[name]; !ok { return api.ErrNotFound } - cli.config.CurrentContext = name - return cli.config.Save() + cli.Config.CurrentContext = name + return cli.Config.Save() } // ConnectCluster connects to a cluster using the given context name or the current context if not specified. @@ -71,42 +71,42 @@ func (cli *CLI) ConnectCluster(ctx context.Context, contextName string) (*client return connectCluster(ctx, *cli.conn) } - if len(cli.config.Contexts) == 0 { + if len(cli.Config.Contexts) == 0 { return nil, fmt.Errorf( "no cluster contexts found in the Uncloud config (%s). "+ "Please initialise a cluster with 'uncloud machine init' first", - cli.config.Path(), + cli.Config.Path(), ) } if contextName == "" { // If the cluster is not specified, use the current cluster if set. - if cli.config.CurrentContext == "" { + if cli.Config.CurrentContext == "" { return nil, fmt.Errorf( "the current cluster context is not set in the Uncloud config (%s). "+ "Please specify the context with the '--context' flag or set 'current_context' in the config", - cli.config.Path(), + cli.Config.Path(), ) } - if _, ok := cli.config.Contexts[cli.config.CurrentContext]; !ok { + if _, ok := cli.Config.Contexts[cli.Config.CurrentContext]; !ok { return nil, fmt.Errorf( "current cluster context '%s' not found in the Uncloud config (%s). "+ "Please specify the context with the '--context' flag or update 'current_context' in the config", - cli.config.CurrentContext, - cli.config.Path(), + cli.Config.CurrentContext, + cli.Config.Path(), ) } - contextName = cli.config.CurrentContext + contextName = cli.Config.CurrentContext } - cfg, ok := cli.config.Contexts[contextName] + cfg, ok := cli.Config.Contexts[contextName] if !ok { return nil, fmt.Errorf("cluster context '%s' not found in the Uncloud config (%s)", - contextName, cli.config.Path()) + contextName, cli.Config.Path()) } if len(cfg.Connections) == 0 { return nil, fmt.Errorf( "no connection configurations found for cluster context '%s' in the Uncloud config (%s)", - contextName, cli.config.Path(), + contextName, cli.Config.Path(), ) } @@ -172,7 +172,7 @@ func (cli *CLI) initRemoteMachine( if contextName == "" { contextName = defaultContextName } - if _, ok := cli.config.Contexts[contextName]; ok { + if _, ok := cli.Config.Contexts[contextName]; ok { return nil, fmt.Errorf("cluster %q already exists", contextName) } @@ -216,12 +216,12 @@ func (cli *CLI) initRemoteMachine( return nil, fmt.Errorf("init cluster: %w", err) } fmt.Printf("Cluster initialised with machine '%s' and saved as context '%s' in your local config (%s)\n", - resp.Machine.Name, contextName, cli.config.Path()) + resp.Machine.Name, contextName, cli.Config.Path()) if err = cli.CreateContext(contextName); err != nil { return nil, fmt.Errorf("save cluster context to config: %w", err) } // Set the current cluster to the just created one if it is the only cluster in the config. - if len(cli.config.Contexts) == 1 { + if len(cli.Config.Contexts) == 1 { if err = cli.SetCurrentContext(contextName); err != nil { return nil, fmt.Errorf("set current cluster context: %w", err) } @@ -232,8 +232,8 @@ func (cli *CLI) initRemoteMachine( SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), SSHKeyFile: remoteMachine.KeyPath, } - cli.config.Contexts[contextName].Connections = append(cli.config.Contexts[contextName].Connections, connCfg) - if err = cli.config.Save(); err != nil { + cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg) + if err = cli.Config.Save(); err != nil { return nil, fmt.Errorf("save config: %w", err) } return machineClient, nil @@ -335,10 +335,10 @@ func (cli *CLI) AddMachine( SSHKeyFile: remoteMachine.KeyPath, } if contextName == "" { - contextName = cli.config.CurrentContext + contextName = cli.Config.CurrentContext } - cli.config.Contexts[contextName].Connections = append(cli.config.Contexts[contextName].Connections, connCfg) - if err = cli.config.Save(); err != nil { + cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg) + if err = cli.Config.Save(); err != nil { return nil, fmt.Errorf("save config: %w", err) }