From d25864e52f2cb3cb4eb7d29ac4f6f58a2b62a3f8 Mon Sep 17 00:00:00 2001 From: Pasha Sviderski Date: Mon, 8 Sep 2025 20:35:17 +1000 Subject: [PATCH] feat: automatically generate a unique 'default-N' context name for new cluster (fixes #113) --- cmd/uncloud/machine/init.go | 2 +- internal/cli/cli.go | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/cmd/uncloud/machine/init.go b/cmd/uncloud/machine/init.go index 0a1679fc..0e34fb08 100644 --- a/cmd/uncloud/machine/init.go +++ b/cmd/uncloud/machine/init.go @@ -89,7 +89,7 @@ func NewInitCommand() *cobra.Command { "Version of the Uncloud daemon to install on the machine.", ) cmd.Flags().StringVarP( - &opts.context, "context", "c", "default", + &opts.context, "context", "c", cli.DefaultContextName, "Name of the created context for the initialised cluster in the Uncloud config.", ) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 5a5acf0a..2ae736a7 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -26,7 +26,7 @@ const ( // DefaultSSHKeyPath is the fallback location for the SSH private key when provisioning remote machines. // Used when no key is explicitly provided and SSH agent authentication fails. DefaultSSHKeyPath = "~/.ssh/id_ed25519" - defaultContextName = "default" + DefaultContextName = "default" ) type CLI struct { @@ -170,12 +170,9 @@ func (cli *CLI) InitCluster(ctx context.Context, opts InitClusterOptions) (*clie } func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) (*client.Client, error) { - contextName := opts.Context - if contextName == "" { - contextName = defaultContextName - } - if _, ok := cli.Config.Contexts[contextName]; ok { - return nil, fmt.Errorf("cluster context '%s' already exists", contextName) + contextName, err := cli.newContextName(opts.Context) + if err != nil { + return nil, err } machineClient, err := provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version) @@ -250,6 +247,32 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) return machineClient, nil } +// newContextName returns a unique name for a new cluster context. If the provided name is not DefaultContextName, +// and it's already taken, an error is returned. If the name is not provided or is DefaultContextName, the first +// available name "default[-N]" is returned. +func (cli *CLI) newContextName(name string) (string, error) { + if name == "" { + name = DefaultContextName + } + + if _, exists := cli.Config.Contexts[name]; !exists { + return name, nil + } + + // If non-default context already exists, error out. + if name != DefaultContextName { + return "", fmt.Errorf("cluster context '%s' already exists", name) + } + + // The default context already exists, generate a numbered suffix to make it unique. + for i := 1; ; i++ { + name = fmt.Sprintf("%s-%d", DefaultContextName, i) + if _, exists := cli.Config.Contexts[name]; !exists { + return name, nil + } + } +} + type AddMachineOptions struct { Context string MachineName string