Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference/manual/hcloud_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ Manage contexts
* [hcloud context create](hcloud_context_create.md) - Create a new context
* [hcloud context delete](hcloud_context_delete.md) - Delete a context
* [hcloud context list](hcloud_context_list.md) - List contexts
* [hcloud context rename](hcloud_context_rename.md) - Rename a context
* [hcloud context unset](hcloud_context_unset.md) - Unset used context
* [hcloud context use](hcloud_context_use.md) - Use a context
29 changes: 29 additions & 0 deletions docs/reference/manual/hcloud_context_rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## hcloud context rename

Rename a context

```
hcloud context rename <context> <name>
```

### Options

```
-h, --help help for rename
```

### Options inherited from parent commands

```
--config string Config file path (default "~/.config/hcloud/cli.toml")
--context string Currently active context
--debug Enable debug output
--debug-file string File to write debug output to
--endpoint string Hetzner Cloud API endpoint (default "https://api.hetzner.cloud/v1")
--poll-interval duration Interval at which to poll information, for example action progress (default 500ms)
--quiet If true, only print error messages
```

### SEE ALSO

* [hcloud context](hcloud_context.md) - Manage contexts
1 change: 1 addition & 0 deletions internal/cmd/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func NewCommand(s state.State) *cobra.Command {
NewUseCommand(s),
NewDeleteCommand(s),
NewListCommand(s),
NewRenameCommand(s),
)
return cmd
}
45 changes: 45 additions & 0 deletions internal/cmd/context/rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package context

import (
"fmt"

"github.com/spf13/cobra"

"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/hetznercloud/cli/internal/cmd/util"
"github.com/hetznercloud/cli/internal/state"
"github.com/hetznercloud/cli/internal/state/config"
)

func NewRenameCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: "rename <context> <name>",
Short: "Rename a context",
Args: util.Validate,
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidates(config.ContextNames(s.Config())...)),
TraverseChildren: true,
DisableFlagsInUseLine: true,
SilenceUsage: true,
RunE: state.Wrap(s, runRename),
}
return cmd
}

func runRename(s state.State, _ *cobra.Command, args []string) error {
originalName, newName := args[0], args[1]
cfg := s.Config()
context := config.ContextByName(cfg, originalName)
if context == nil {
return fmt.Errorf("context not found: %v", originalName)
}
isActive := cfg.ActiveContext() == context
if config.ContextByName(cfg, newName) != nil {
return fmt.Errorf("context with name %v already exists", newName)
}
config.RenameContext(context, newName)
if isActive {
// re-set the active context to ensure the name is updated
cfg.SetActiveContext(context)
}
return cfg.Write(nil)
}
99 changes: 99 additions & 0 deletions internal/cmd/context/rename_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package context_test

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hetznercloud/cli/internal/cmd/context"
"github.com/hetznercloud/cli/internal/testutil"
)

func TestRename(t *testing.T) {
testConfig := `
active_context = "my-context"

[[contexts]]
name = "my-context"
token = "super secret token"

[[contexts]]
name = "my-other-context"
token = "super secret token"
`

type testCase struct {
name string
args []string
config string
err string
expErr string
expOut string
}

testCases := []testCase{
{
name: "rename active context",
args: []string{"my-context", "my-renamed-context"},
config: testConfig,
expOut: `active_context = "my-renamed-context"

[[contexts]]
name = "my-renamed-context"
token = "super secret token"

[[contexts]]
name = "my-other-context"
token = "super secret token"
`,
},
{
name: "rename inactive context",
args: []string{"my-other-context", "my-other-renamed-context"},
config: testConfig,
expOut: `active_context = "my-context"

[[contexts]]
name = "my-context"
token = "super secret token"

[[contexts]]
name = "my-other-renamed-context"
token = "super secret token"
`,
},
{
name: "rename non-existing context",
args: []string{"non-existing-context", "non-existing-renamed-context"},
config: testConfig,
err: "context not found: non-existing-context",
expErr: "Error: context not found: non-existing-context\n",
},
{
name: "rename to existing context",
args: []string{"my-context", "my-other-context"},
config: testConfig,
err: "context with name my-other-context already exists",
expErr: "Error: context with name my-other-context already exists\n",
},
}

for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
fx := testutil.NewFixtureWithConfigFile(t, []byte(tt.config))
defer fx.Finish()

cmd := context.NewRenameCommand(fx.State())
out, errOut, err := fx.Run(cmd, tt.args)

if tt.err == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.err)
}
assert.Equal(t, tt.expErr, errOut)
assert.Equal(t, tt.expOut, out)
})
}
}
6 changes: 6 additions & 0 deletions internal/state/config/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ func RemoveContext(cfg Config, context Context) {
}
cfg.SetContexts(filtered)
}

func RenameContext(ctx Context, newName string) {
if ctx, ok := (ctx).(*context); ok {
ctx.ContextName = newName
}
}