Skip to content

Commit

Permalink
Shell completions (#266)
Browse files Browse the repository at this point in the history
* Update dependencies

* Rework command shell completion

This commit removes all custom shell completion functions. Instead it
configures cobra to generate completion functions. Where necessary and
possible completion values are obtained from the Hetzner Cloud API.

Additionally this commit lays the ground work for splitting the cli
package into smaller and more manageable packages. Especially the
cli.CLI type has been prepared for removal.
  • Loading branch information
fhofherr authored Aug 24, 2020
1 parent 290c168 commit ad3a564
Show file tree
Hide file tree
Showing 146 changed files with 1,558 additions and 836 deletions.
5 changes: 0 additions & 5 deletions cli/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ func newCertificatesCommand(cli *CLI) *cobra.Command {
Args: cobra.NoArgs,
TraverseChildren: true,
DisableFlagsInUseLine: true,
RunE: cli.wrap(runCertificates),
}
cmd.AddCommand(
newCertificatesListCommand(cli),
Expand All @@ -23,7 +22,3 @@ func newCertificatesCommand(cli *CLI) *cobra.Command {

return cmd
}

func runCertificates(cli *CLI, cmd *cobra.Command, args []string) error {
return cmd.Usage()
}
2 changes: 2 additions & 0 deletions cli/certificate_add_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"

"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)
Expand All @@ -12,6 +13,7 @@ func newCertificateAddLabelCommand(cli *CLI) *cobra.Command {
Use: "add-label [FLAGS] CERTIFICATE LABEL",
Short: "Add a label to a certificate",
Args: cobra.ExactArgs(2),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.CertificateNames)),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: chainRunE(validateCertificateAddLabel, cli.ensureToken),
Expand Down
2 changes: 2 additions & 0 deletions cli/certificate_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"

"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/spf13/cobra"
)

Expand All @@ -11,6 +12,7 @@ func newCertificateDeleteCommand(cli *CLI) *cobra.Command {
Use: "delete CERTIFICATE",
Short: "Delete a certificate",
Args: cobra.ExactArgs(1),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.CertificateNames)),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: cli.ensureToken,
Expand Down
2 changes: 2 additions & 0 deletions cli/certificate_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

humanize "github.com/dustin/go-humanize"
"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)
Expand All @@ -14,6 +15,7 @@ func newCertificateDescribeCommand(cli *CLI) *cobra.Command {
Use: "describe [FLAGS] CERTIFICATE",
Short: "Describe a certificate",
Args: cobra.ExactArgs(1),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.CertificateNames)),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: cli.ensureToken,
Expand Down
16 changes: 13 additions & 3 deletions cli/certificate_remove_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import (
"errors"
"fmt"

"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)

func newCertificateRemoveLabelCommand(cli *CLI) *cobra.Command {
cmd := &cobra.Command{
Use: "remove-label [FLAGS] CERTIFICATE LABELKEY",
Short: "Remove a label from a certificate",
Args: cobra.RangeArgs(1, 2),
Use: "remove-label [FLAGS] CERTIFICATE LABELKEY",
Short: "Remove a label from a certificate",
Args: cobra.RangeArgs(1, 2),
ValidArgsFunction: cmpl.SuggestArgs(
cmpl.SuggestCandidatesF(cli.CertificateNames),
cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string {
if len(args) != 1 {
return nil
}
return cli.CertificateLabelKeys(args[0])
}),
),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: chainRunE(validateCertificateRemoveLabel, cli.ensureToken),
Expand Down
2 changes: 2 additions & 0 deletions cli/certificate_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"

"github.com/hetznercloud/cli/internal/cmd/cmpl"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)
Expand All @@ -12,6 +13,7 @@ func newCertificateUpdateCommand(cli *CLI) *cobra.Command {
Use: "update [FLAGS] CERTIFICATE",
Short: "Update an existing Certificate",
Args: cobra.ExactArgs(1),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.CertificateNames)),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: cli.ensureToken,
Expand Down
231 changes: 208 additions & 23 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (
"log"
"os"
"path/filepath"
"strconv"

"github.com/cheggaaa/pb/v3"
"github.com/hetznercloud/cli/internal/hcapi"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
Expand All @@ -34,10 +34,18 @@ type CLI struct {

RootCommand *cobra.Command

client *hcloud.Client

serverNames map[int]string
networkNames map[int]string
client *hcloud.Client
isoClient *hcapi.ISOClient
imageClient *hcapi.ImageClient
locationClient *hcapi.LocationClient
dataCenterClient *hcapi.DataCenterClient
sshKeyClient *hcapi.SSHKeyClient
volumeClient *hcapi.VolumeClient
certificateClient *hcapi.CertificateClient
floatingIPClient *hcapi.FloatingIPClient
networkClient *hcapi.NetworkClient
loadBalancerClient *hcapi.LoadBalancerClient
serverClient *hcapi.ServerClient
}

func NewCLI() *CLI {
Expand Down Expand Up @@ -158,6 +166,110 @@ func (c *CLI) Client() *hcloud.Client {
return c.client
}

func (c *CLI) CertificateNames() []string {
if c.certificateClient == nil {
client := c.Client()
c.certificateClient = &hcapi.CertificateClient{CertificateClient: &client.Certificate}
}
return c.certificateClient.CertificateNames()
}

func (c *CLI) CertificateLabelKeys(idOrName string) []string {
if c.certificateClient == nil {
client := c.Client()
c.certificateClient = &hcapi.CertificateClient{CertificateClient: &client.Certificate}
}
return c.certificateClient.CertificateLabelKeys(idOrName)
}

func (c *CLI) FloatingIPNames() []string {
if c.floatingIPClient == nil {
client := c.Client()
c.floatingIPClient = &hcapi.FloatingIPClient{FloatingIPClient: &client.FloatingIP}
}
return c.floatingIPClient.FloatingIPNames()
}

func (c *CLI) FloatingIPLabelKeys(idOrName string) []string {
if c.floatingIPClient == nil {
client := c.Client()
c.floatingIPClient = &hcapi.FloatingIPClient{FloatingIPClient: &client.FloatingIP}
}
return c.floatingIPClient.FloatingIPLabelKeys(idOrName)
}

func (c *CLI) ISONames() []string {
if c.isoClient == nil {
client := c.Client()
c.isoClient = &hcapi.ISOClient{ISOClient: &client.ISO}
}
return c.isoClient.ISONames()
}

func (c *CLI) ImageNames() []string {
if c.imageClient == nil {
client := c.Client()
c.imageClient = &hcapi.ImageClient{ImageClient: &client.Image}
}
return c.isoClient.ISONames()
}

func (c *CLI) ImageLabelKeys(idOrName string) []string {
if c.imageClient == nil {
client := c.Client()
c.imageClient = &hcapi.ImageClient{ImageClient: &client.Image}
}
return c.imageClient.ImageLabelKeys(idOrName)
}

func (c *CLI) LocationNames() []string {
if c.locationClient == nil {
client := c.Client()
c.locationClient = &hcapi.LocationClient{LocationClient: &client.Location}
}
return c.locationClient.LocationNames()
}

func (c *CLI) DataCenterNames() []string {
if c.dataCenterClient == nil {
client := c.Client()
c.dataCenterClient = &hcapi.DataCenterClient{DatacenterClient: &client.Datacenter}
}
return c.dataCenterClient.DataCenterNames()
}

func (c *CLI) SSHKeyNames() []string {
if c.sshKeyClient == nil {
client := c.Client()
c.sshKeyClient = &hcapi.SSHKeyClient{SSHKeyClient: &client.SSHKey}
}
return c.sshKeyClient.SSHKeyNames()
}

func (c *CLI) SSHKeyLabelKeys(idOrName string) []string {
if c.sshKeyClient == nil {
client := c.Client()
c.sshKeyClient = &hcapi.SSHKeyClient{SSHKeyClient: &client.SSHKey}
}
return c.sshKeyClient.SSHKeyLabelKeys(idOrName)
}

func (c *CLI) VolumeNames() []string {
if c.volumeClient == nil {
client := c.Client()
c.volumeClient = &hcapi.VolumeClient{VolumeClient: &client.Volume}
}
return c.volumeClient.VolumeNames()
}

func (c *CLI) VolumeLabelKeys(idOrName string) []string {
if c.volumeClient == nil {
client := c.Client()
c.volumeClient = &hcapi.VolumeClient{VolumeClient: &client.Volume}
}
return c.volumeClient.VolumeLabelKeys(idOrName)
}

// Terminal returns whether the CLI is run in a terminal.
func (c *CLI) Terminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
Expand Down Expand Up @@ -247,30 +359,103 @@ func (c *CLI) WaitForActions(ctx context.Context, actions []*hcloud.Action) erro
return nil
}

func (c *CLI) GetServerName(id int) string {
if c.serverNames == nil {
c.serverNames = map[int]string{}
servers, _ := c.Client().Server.All(c.Context)
for _, server := range servers {
c.serverNames[server.ID] = server.Name
func (c *CLI) ServerTypeNames() []string {
if c.serverClient == nil {
client := c.Client()
c.serverClient = &hcapi.ServerClient{
ServerClient: &client.Server,
ServerTypes: &client.ServerType,
}
}
if serverName, ok := c.serverNames[id]; ok {
return serverName
return c.serverClient.ServerTypeNames()
}

func (c *CLI) ServerNames() []string {
if c.serverClient == nil {
client := c.Client()
c.serverClient = &hcapi.ServerClient{
ServerClient: &client.Server,
ServerTypes: &client.ServerType,
}
}
return strconv.Itoa(id)
return c.serverClient.ServerNames()
}

func (c *CLI) GetNetworkName(id int) string {
if c.networkNames == nil {
c.networkNames = map[int]string{}
networks, _ := c.Client().Network.All(c.Context)
for _, network := range networks {
c.networkNames[network.ID] = network.Name
func (c *CLI) ServerLabelKeys(idOrName string) []string {
if c.serverClient == nil {
client := c.Client()
c.serverClient = &hcapi.ServerClient{
ServerClient: &client.Server,
ServerTypes: &client.ServerType,
}
}
if networkName, ok := c.networkNames[id]; ok {
return networkName
return c.serverClient.ServerLabelKeys(idOrName)
}

func (c *CLI) ServerName(id int) string {
if c.serverClient == nil {
client := c.Client()
c.serverClient = &hcapi.ServerClient{
ServerClient: &client.Server,
ServerTypes: &client.ServerType,
}
}
return c.serverClient.ServerName(id)
}

func (c *CLI) NetworkNames() []string {
if c.networkClient == nil {
client := c.Client()
c.networkClient = &hcapi.NetworkClient{NetworkClient: &client.Network}
}
return c.networkClient.NetworkNames()
}

func (c *CLI) NetworkName(id int) string {
if c.networkClient == nil {
client := c.Client()
c.networkClient = &hcapi.NetworkClient{NetworkClient: &client.Network}
}
return c.networkClient.NetworkName(id)
}

func (c *CLI) NetworkLabelKeys(idOrName string) []string {
if c.networkClient == nil {
client := c.Client()
c.networkClient = &hcapi.NetworkClient{NetworkClient: &client.Network}
}
return c.networkClient.NetworkLabelKeys(idOrName)
}

func (c *CLI) LoadBalancerNames() []string {
if c.loadBalancerClient == nil {
client := c.Client()
c.loadBalancerClient = &hcapi.LoadBalancerClient{
LoadBalancerClient: &client.LoadBalancer,
TypeClient: &client.LoadBalancerType,
}
}
return c.loadBalancerClient.LoadBalancerNames()
}

func (c *CLI) LoadBalancerLabelKeys(idOrName string) []string {
if c.loadBalancerClient == nil {
client := c.Client()
c.loadBalancerClient = &hcapi.LoadBalancerClient{
LoadBalancerClient: &client.LoadBalancer,
TypeClient: &client.LoadBalancerType,
}
}
return c.loadBalancerClient.LoadBalancerLabelKeys(idOrName)
}

func (c *CLI) LoadBalancerTypeNames() []string {
if c.loadBalancerClient == nil {
client := c.Client()
c.loadBalancerClient = &hcapi.LoadBalancerClient{
LoadBalancerClient: &client.LoadBalancer,
TypeClient: &client.LoadBalancerType,
}
}
return strconv.Itoa(id)
return c.loadBalancerClient.LoadBalancerTypeNames()
}
Loading

0 comments on commit ad3a564

Please sign in to comment.