Skip to content

Commit

Permalink
feat: group subcommands in command help (#675)
Browse files Browse the repository at this point in the history
This PR adds subcommand groups, which make the help output more
structured and easier to read. Conventions on which groups to add and
how to name them is also added.

---------

Co-authored-by: pauhull <[email protected]>
  • Loading branch information
phm07 and phm07 authored Feb 1, 2024
1 parent e8f3620 commit 0cb271f
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 71 deletions.
110 changes: 107 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,130 @@
# Developing hcloud-cli

## Generated files

This repository contains generated files, mainly for testing purposes. These files are generated by running

```sh
go generate ./...
```

in the root directory of this repository. Make sure to keep generated files up-to-date
when making changes to the code.

## Unit tests
Unit tests are located in the `internal` directory. Run them with

Unit tests are located in the `internal` directory. Run them with:

```sh
go test ./...
```

## Build
To build the binary, run

To build the binary, run:

```sh
go build -o hcloud-cli ./cmd/hcloud
```

To include version information in the resulting binary and build for all targets, use GoReleaser:
To include version information in the resulting binary and build for all targets, use GoReleaser:

```sh
goreleaser --snapshot --skip-publish --rm-dist
```

## Conventions

### Subcommand groups

Cobra offers the functionality to group subcommands. The conventions on when and how to group commands are as follows:

1. Use the following Categories:
- **General** (CRUD operations such as `create`, `delete`, `describe`, `list`, `update` + Label
commands `add-`/`remove-label`)
- Groups based on actions (e.g. `enable`, `disable`, `attach`, `detach`), for example `enable-`/`disable-protection`
or `poweron`/`poweroff`/`shutdown`/`reset`/`reboot`
- **Additional commands** that don't fit into the other categories (`Group.ID` is empty). These should be
utility commands, for example ones that perform client-side actions.
2. Groups are only needed if more than the "General" group commands are present.
3. `Group.ID` formatting:
1. If the `ID` is a noun, it should be singular.
2. If the `ID` is a verb, it should be in the infinitive form.
3. The `ID` should be in kebab-case.
4. `Group.Title` formatting:
1. Should be the `ID` but capitalized and with spaces instead of dashes.
2. If a single resource is managed, the `Title` should be singular. Otherwise, it should be plural.

Example: Multiple network can be attached to server -> `Networks`, but only one ISO can be attached to a server -> `ISO`

Here is how to create a group:

```go
// General
util.AddGroup(cmd, "general", "General",
SomeGeneralCommand.CobraCommand(s),
// ...
)

// Additional commands
cmd.AddCommand(
SomeUtilCommand.CobraCommand(s),
// ...
)
```

Example of the `hcloud server` command groups:

```
General:
add-label Add a label to a server
change-type Change type of a server
create Create a server
create-image Create an image from a server
delete Delete a server
describe Describe a server
list List Servers
rebuild Rebuild a server
remove-label Remove a label from a server
update Update a Server
Protection:
disable-protection Disable resource protection for a server
enable-protection Enable resource protection for a server
Rescue:
disable-rescue Disable rescue for a server
enable-rescue Enable rescue for a server
Power/Reboot:
poweroff Poweroff a server
poweron Poweron a server
reboot Reboot a server
reset Reset a server
shutdown Shutdown a server
Networks:
attach-to-network Attach a server to a network
change-alias-ips Change a server's alias IPs in a network
detach-from-network Detach a server from a network
ISO:
attach-iso Attach an ISO to a server
detach-iso Detach an ISO from a server
Placement Groups:
add-to-placement-group Add a server to a placement group
remove-from-placement-group Removes a server from a placement group
Backup:
disable-backup Disable backup for a server
enable-backup Enable backup for a server
Additional Commands:
ip Print a server's IP address
metrics [ALPHA] Metrics from a Server
request-console Request a WebSocket VNC console for a server
reset-password Reset the root password of a server
set-rdns Change reverse DNS of a Server
ssh Spawn an SSH connection for the server
```
13 changes: 9 additions & 4 deletions cmd/hcloud/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hetznercloud/cli/internal/cmd/server"
"github.com/hetznercloud/cli/internal/cmd/servertype"
"github.com/hetznercloud/cli/internal/cmd/sshkey"
"github.com/hetznercloud/cli/internal/cmd/util"
"github.com/hetznercloud/cli/internal/cmd/version"
"github.com/hetznercloud/cli/internal/cmd/volume"
"github.com/hetznercloud/cli/internal/state"
Expand Down Expand Up @@ -52,16 +53,14 @@ func main() {
}

rootCommand := cli.NewRootCommand(s)
rootCommand.AddCommand(

util.AddGroup(rootCommand, "resource", "Resources",
all.NewCommand(s),
floatingip.NewCommand(s),
image.NewCommand(s),
server.NewCommand(s),
sshkey.NewCommand(s),
version.NewCommand(s),
completion.NewCommand(s),
servertype.NewCommand(s),
context.NewCommand(s),
datacenter.NewCommand(s),
location.NewCommand(s),
iso.NewCommand(s),
Expand All @@ -75,6 +74,12 @@ func main() {
primaryip.NewCommand(s),
)

rootCommand.AddCommand(
version.NewCommand(s),
completion.NewCommand(s),
context.NewCommand(s),
)

if err := rootCommand.Execute(); err != nil {
log.Fatalln(err)
}
Expand Down
16 changes: 12 additions & 4 deletions internal/cmd/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package firewall
import (
"github.com/spf13/cobra"

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

Expand All @@ -14,19 +15,26 @@ func NewCommand(s state.State) *cobra.Command {
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
cmd.AddCommand(

util.AddGroup(cmd, "general", "General",
ListCmd.CobraCommand(s),
DescribeCmd.CobraCommand(s),
CreateCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
UpdateCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
)

util.AddGroup(cmd, "rule", "Rules",
ReplaceRulesCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
AddRuleCmd.CobraCommand(s),
DeleteRuleCmd.CobraCommand(s),
)

util.AddGroup(cmd, "resource", "Resources",
ApplyToResourceCmd.CobraCommand(s),
RemoveFromResourceCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
)
return cmd
}
25 changes: 17 additions & 8 deletions internal/cmd/floatingip/floatingip.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package floatingip
import (
"github.com/spf13/cobra"

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

Expand All @@ -14,19 +15,27 @@ func NewCommand(s state.State) *cobra.Command {
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
UpdateCmd.CobraCommand(s),

util.AddGroup(cmd, "general", "General",
ListCmd.CobraCommand(s),
CreateCmd.CobraCommand(s),
DescribeCmd.CobraCommand(s),
AssignCmd.CobraCommand(s),
UnassignCmd.CobraCommand(s),
CreateCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
UpdateCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
SetRDNSCmd.CobraCommand(s),
)

util.AddGroup(cmd, "protection", "Protection",
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
)

util.AddGroup(cmd, "assign", "Assign",
AssignCmd.CobraCommand(s),
UnassignCmd.CobraCommand(s),
)

cmd.AddCommand(SetRDNSCmd.CobraCommand(s))
return cmd
}
13 changes: 9 additions & 4 deletions internal/cmd/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package image
import (
"github.com/spf13/cobra"

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

Expand All @@ -14,15 +15,19 @@ func NewCommand(s state.State) *cobra.Command {
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
cmd.AddCommand(

util.AddGroup(cmd, "general", "General",
ListCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
DescribeCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
UpdateCmd.CobraCommand(s),
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
)

util.AddGroup(cmd, "protection", "Protection",
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
)
return cmd
}
34 changes: 27 additions & 7 deletions internal/cmd/loadbalancer/load_balancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package loadbalancer
import (
"github.com/spf13/cobra"

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

Expand All @@ -15,27 +16,46 @@ func NewCommand(s state.State) *cobra.Command {
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
CreateCmd.CobraCommand(s),

util.AddGroup(cmd, "general", "General",
ListCmd.CobraCommand(s),
CreateCmd.CobraCommand(s),
DescribeCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
UpdateCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
ChangeAlgorithmCmd.CobraCommand(s),
ChangeTypeCmd.CobraCommand(s),
)

util.AddGroup(cmd, "protection", "Protection",
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
)

util.AddGroup(cmd, "target", "Targets",
AddTargetCmd.CobraCommand(s),
RemoveTargetCmd.CobraCommand(s),
ChangeAlgorithmCmd.CobraCommand(s),
)

util.AddGroup(cmd, "service", "Services",
AddServiceCmd.CobraCommand(s),
UpdateServiceCmd.CobraCommand(s),
DeleteServiceCmd.CobraCommand(s),
AddServiceCmd.CobraCommand(s),
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
)

util.AddGroup(cmd, "network", "Network",
AttachToNetworkCmd.CobraCommand(s),
DetachFromNetworkCmd.CobraCommand(s),
)

util.AddGroup(cmd, "public-interface", "Public Interface",
EnablePublicInterfaceCmd.CobraCommand(s),
DisablePublicInterfaceCmd.CobraCommand(s),
ChangeTypeCmd.CobraCommand(s),
)

cmd.AddCommand(
MetricsCmd.CobraCommand(s),
SetRDNSCmd.CobraCommand(s),
)
Expand Down
25 changes: 18 additions & 7 deletions internal/cmd/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package network
import (
"github.com/spf13/cobra"

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

Expand All @@ -14,22 +15,32 @@ func NewCommand(s state.State) *cobra.Command {
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
cmd.AddCommand(

util.AddGroup(cmd, "general", "General",
ListCmd.CobraCommand(s),
DescribeCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
CreateCmd.CobraCommand(s),
UpdateCmd.CobraCommand(s),
DeleteCmd.CobraCommand(s),
ChangeIPRangeCmd.CobraCommand(s),
AddRouteCmd.CobraCommand(s),
RemoveRouteCmd.CobraCommand(s),
AddSubnetCmd.CobraCommand(s),
RemoveSubnetCmd.CobraCommand(s),
LabelCmds.AddCobraCommand(s),
LabelCmds.RemoveCobraCommand(s),
ChangeIPRangeCmd.CobraCommand(s),
)

util.AddGroup(cmd, "protection", "Protection",
EnableProtectionCmd.CobraCommand(s),
DisableProtectionCmd.CobraCommand(s),
)

util.AddGroup(cmd, "route", "Routes",
AddRouteCmd.CobraCommand(s),
RemoveRouteCmd.CobraCommand(s),
ExposeRoutesToVSwitchCmd.CobraCommand(s),
)

util.AddGroup(cmd, "subnet", "Subnets",
AddSubnetCmd.CobraCommand(s),
RemoveSubnetCmd.CobraCommand(s),
)
return cmd
}
Loading

0 comments on commit 0cb271f

Please sign in to comment.