diff --git a/cli/certificate.go b/cli/certificate.go index 51dc4f23..0f5b377a 100644 --- a/cli/certificate.go +++ b/cli/certificate.go @@ -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), @@ -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() -} diff --git a/cli/certificate_add_label.go b/cli/certificate_add_label.go index 80fe4c7f..8260363c 100644 --- a/cli/certificate_add_label.go +++ b/cli/certificate_add_label.go @@ -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" ) @@ -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), diff --git a/cli/certificate_delete.go b/cli/certificate_delete.go index 4ac3e454..5566c791 100644 --- a/cli/certificate_delete.go +++ b/cli/certificate_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -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, diff --git a/cli/certificate_describe.go b/cli/certificate_describe.go index 81d50a94..a2283aa0 100644 --- a/cli/certificate_describe.go +++ b/cli/certificate_describe.go @@ -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" ) @@ -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, diff --git a/cli/certificate_remove_label.go b/cli/certificate_remove_label.go index b267d7e1..f8f65f29 100644 --- a/cli/certificate_remove_label.go +++ b/cli/certificate_remove_label.go @@ -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), diff --git a/cli/certificate_update.go b/cli/certificate_update.go index 2ddba6bd..f8ac43c1 100644 --- a/cli/certificate_update.go +++ b/cli/certificate_update.go @@ -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" ) @@ -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, diff --git a/cli/cli.go b/cli/cli.go index cebd28ce..daf881b1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -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" @@ -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 { @@ -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())) @@ -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() } diff --git a/cli/completion.go b/cli/completion.go index 1b7fa20b..8cb04bbf 100644 --- a/cli/completion.go +++ b/cli/completion.go @@ -1,562 +1,87 @@ package cli import ( - "bytes" "fmt" - "io" "os" "github.com/spf13/cobra" ) -const ( - bashCompletionFunc = ` - __hcloud_sshkey_names() { - local ctl_output out - if ctl_output=$(hcloud ssh-key list -o noheader -o columns=name 2>/dev/null); then - IFS=$'\n' - COMPREPLY=($(echo "${ctl_output}" | while read -r line; do printf "%q\n" "$line"; done)) - fi - } - - __hcloud_context_names() { - local ctl_output out - if ctl_output=$(hcloud context list -o noheader 2>/dev/null); then - IFS=$'\n' - COMPREPLY=($(echo "${ctl_output}" | while read -r line; do printf "%q\n" "$line"; done)) - fi - } - - __hcloud_floatingip_ids() { - local ctl_output out - if ctl_output=$(hcloud floating-ip list -o noheader -o columns=id 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_volume_names() { - local ctl_output out - if ctl_output=$(hcloud volume list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_network_names() { - local ctl_output out - if ctl_output=$(hcloud network list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } +// const ( +// completionShortDescription = "Output shell completion code for the specified shell (bash or zsh)" +// completionLongDescription = completionShortDescription + ` - __hcloud_iso_names() { - local ctl_output out - if ctl_output=$(hcloud iso list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_datacenter_names() { - local ctl_output out - if ctl_output=$(hcloud datacenter list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_location_names() { - local ctl_output out - if ctl_output=$(hcloud location list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_server_names() { - local ctl_output out - if ctl_output=$(hcloud server list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_servertype_names() { - local ctl_output out - if ctl_output=$(hcloud server-type list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } +// Note: this requires the bash-completion framework, which is not installed by default on Mac. This can be installed by using homebrew: - __hcloud_load_balancer_names() { - local ctl_output out - if ctl_output=$(hcloud load-balancer list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_load_balancer_type_names() { - local ctl_output out - if ctl_output=$(hcloud load-balancer-type list -o noheader -o columns=name 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}")) - fi - } - - __hcloud_image_ids_no_system() { - local ctl_output out - if ctl_output=$(hcloud image list -o noheader 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}" | awk '{if ($2 != "system") {print $1}}')) - fi - } - - __hcloud_image_names() { - local ctl_output out - if ctl_output=$(hcloud image list -o noheader 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}" | awk '{if ($3 == "-") {print $1} else {print $3}}')) - fi - } - - __hcloud_floating_ip_ids() { - local ctl_output out - if ctl_output=$(hcloud floating-ip list -o noheader 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}" | awk '{print $1}')) - fi - } - - __hcloud_certificate_names() { - local ctl_output out - if ctl_output=$(hcloud certificate list -o noheader 2>/dev/null); then - COMPREPLY=($(echo "${ctl_output}" | awk '{print $2}')) - fi - } - - __hcloud_image_types_no_system() { - COMPREPLY=($(echo "snapshot backup")) - } - - __hcloud_load_balancer_algorithm_types() { - COMPREPLY=($(echo "round_robin least_connections")) - } - - __hcloud_protection_levels() { - COMPREPLY=($(echo "delete")) - } +// $ brew install bash-completion - __hcloud_server_protection_levels() { - COMPREPLY=($(echo "delete rebuild")) - } +// Once installed, bash completion must be evaluated. This can be done by adding the following line to the .bash profile: - __hcloud_floatingip_types() { - COMPREPLY=($(echo "ipv4 ipv6")) - } +// $ source $(brew --prefix)/etc/bash_completion - __hcloud_rescue_types() { - COMPREPLY=($(echo "linux64 linux32 freebsd64")) - } +// Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2 - __hcloud_network_zones() { - COMPREPLY=($(echo "eu-central")) - } +// Examples: +// # Load the hcloud completion code for bash into the current shell +// source <(hcloud completion bash) - __hcloud_network_subnet_types() { - COMPREPLY=($(echo "server")) - } +// # Load the hcloud completion code for zsh into the current shell +// source <(hcloud completion zsh)` +// ) - __custom_func() { - case ${last_command} in - hcloud_server_delete | hcloud_server_describe | \ - hcloud_server_create-image | hcloud_server_poweron | \ - hcloud_server_poweroff | hcloud_server_reboot | \ - hcloud_server_reset | hcloud_server_reset-password | \ - hcloud_server_shutdown | hcloud_server_disable-rescue | \ - hcloud_server_enable-rescue | hcloud_server_detach-iso | \ - hcloud_server_update | hcloud_server_enable-backup | \ - hcloud_server_disable-backup | hcloud_server_rebuild | \ - hcloud_server_add-label | hcloud_server_remove-label | \ - hcloud_server_ssh ) - __hcloud_server_names - return - ;; - hcloud_server_attach-iso ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_iso_names - return - fi - __hcloud_server_names - return - ;; - hcloud_server_change-type ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_servertype_names - return - fi - __hcloud_server_names - return - ;; - hcloud_server-type_describe ) - __hcloud_servertype_names - return - ;; - hcloud_load-balancer-type_describe ) - __hcloud_load_balancer_type_names - return - ;; - hcloud_load-balancer_delete | hcloud_load-balancer_describe | \ - hcloud_load-balancer_update | hcloud_load-balancer_add-label | \ - hcloud_load-balancer_remove-label | hcloud_load-balancer_enable-public-interface | \ - hcloud_load-balancer_disable-public-interface ) - __hcloud_load_balancer_names - return - ;; - hcloud_load-balancer_enable-protection | hcloud_load-balancer_disable-protection ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_protection_levels - return - fi - __hcloud_load_balancer_names - return - ;; - hcloud_load-balancer_change-algorithm ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_load_balancer_algorithm_types - return - fi - __hcloud_load_balancer_names - return - ;; - hcloud_load-balancer_add-target | hcloud_load-balancer_update-service | \ - hcloud_load-balancer_remove-target | hcloud_load-balancer_add-service | \ - hcloud_load-balancer_delete-service | hcloud_load-balancer_update-health-check | \ - hcloud_load-balancer_attach-to-network | hcloud_load-balancer_detach-from-network ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - __hcloud_load_balancer_names - return - ;; - hcloud_image_describe | hcloud_image_add-label | hcloud_image_remove-label ) - __hcloud_image_names - return - ;; - hcloud_image_delete | hcloud_image_update ) - __hcloud_image_ids_no_system - return - ;; - hcloud_floating-ip_assign ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_server_names - return - fi - __hcloud_floating_ip_ids - return - ;; - hcloud_floating-ip_enable-protection | hcloud_floating-ip_disable-protection ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_protection_levels - return - fi - __hcloud_floating_ip_ids - return - ;; - hcloud_image_enable-protection | hcloud_image_disable-protection ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_protection_levels - return - fi - __hcloud_image_ids_no_system - return - ;; - hcloud_server_enable-protection | hcloud_server_disable-protection ) - if [[ ${#nouns[@]} -gt 2 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -gt 0 ]]; then - __hcloud_server_protection_levels - return - fi - __hcloud_server_names - return - ;; - hcloud_volumes_enable-protection | hcloud_volume_disable-protection ) - if [[ ${#nouns[@]} -gt 1 ]]; then - return 1 - fi - if [[ ${#nouns[@]} -eq 1 ]]; then - __hcloud_protection_levels - return - fi - __hcloud_volume_names - return - ;; - hcloud_floating-ip_unassign | hcloud_floating-ip_delete | \ - hcloud_floating-ip_describe | hcloud_floating-ip_update | \ - hcloud_floating-ip_add-label | hcloud_floating-ip_remove-label ) - __hcloud_floating_ip_ids - return - ;; - hcloud_volume_detach | hcloud_volume_delete | \ - hcloud_volume_describe | hcloud_volume_update | \ - hcloud_volume_add-label | hcloud_volume_remove-label ) - __hcloud_volume_names - return - ;; - hcloud_datacenter_describe ) - __hcloud_datacenter_names - return - ;; - hcloud_location_describe ) - __hcloud_location_names - return - ;; - hcloud_iso_describe ) - __hcloud_iso_names - return - ;; - hcloud_load-balancer_describe ) - __hcloud_load_balancer_names - return - ;; - hcloud_context_use | hcloud_context_delete ) - __hcloud_context_names - return - ;; - hcloud_ssh-key_delete | hcloud_ssh-key_describe | \ - hcloud_ssh-key_add-label | hcloud_ssk-key_remove-label) - __hcloud_sshkey_names - return - ;; - hcloud_certificate_describe | hcloud_certificate_update | \ - hcloud_certificate_add-label | hcloud_certificate_remove-label | \ - hcloud_certificate_delete ) - __hcloud_certificate_names - return - ;; - *) - ;; - esac - } - ` +func newCompletionCommand(cli *CLI) *cobra.Command { + cmd := &cobra.Command{ + Use: "completion [FLAGS] SHELL", + Short: "Output shell completion code for the specified shell", + Long: `To load completions: - completionShortDescription = "Output shell completion code for the specified shell (bash or zsh)" - completionLongDescription = completionShortDescription + ` +Bash: -Note: this requires the bash-completion framework, which is not installed by default on Mac. This can be installed by using homebrew: +$ source <(hcloud completion bash) - $ brew install bash-completion +# To load completions for each session, execute once: +Linux: + $ hcloud completion bash > /etc/bash_completion.d/hcloud +MacOS: + $ hcloud completion bash > /usr/local/etc/bash_completion.d/hcloud -Once installed, bash completion must be evaluated. This can be done by adding the following line to the .bash profile: +Zsh: - $ source $(brew --prefix)/etc/bash_completion +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: -Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2 +$ echo "autoload -U compinit; compinit" >> ~/.zshrc -Examples: - # Load the hcloud completion code for bash into the current shell - source <(hcloud completion bash) +# To load completions for each session, execute once: +$ hcloud completion zsh > "${fpath[1]}/_hcloud" - # Load the hcloud completion code for zsh into the current shell - source <(hcloud completion zsh)` -) +# You will need to start a new shell for this setup to take effect. -var ( - completionShells = map[string]func(out io.Writer, cmd *cobra.Command) error{ - "bash": runCompletionBash, - "zsh": runCompletionZsh, - } -) +Fish: -func newCompletionCommand(cli *CLI) *cobra.Command { - shells := []string{} - for s := range completionShells { - shells = append(shells, s) - } +$ hcloud completion fish | source - cmd := &cobra.Command{ - Use: "completion [FLAGS] SHELL", - Short: "Output shell completion code for the specified shell (bash or zsh)", - Long: completionLongDescription, - RunE: cli.wrap(runCompletion), +# To load completions for each session, execute once: +$ hcloud completion fish > ~/.config/fish/completions/hcloud.fish +`, Args: cobra.ExactArgs(1), - ValidArgs: shells, + ValidArgs: []string{"bash", "fish", "zsh"}, DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + var err error + + switch args[0] { + case "bash": + err = cmd.Root().GenBashCompletion(os.Stdout) + case "fish": + err = cmd.Root().GenFishCompletion(os.Stdout, true) + case "zsh": + err = cmd.Root().GenZshCompletion(os.Stdout) + default: + err = fmt.Errorf("Unsupported shell: %s", args[0]) + } + return err + }, } return cmd } - -func runCompletion(cli *CLI, cmd *cobra.Command, args []string) error { - run, found := completionShells[args[0]] - if !found { - return fmt.Errorf("unsupported shell type %q", args[0]) - } - - return run(os.Stdout, cmd.Parent()) -} - -func runCompletionBash(out io.Writer, cmd *cobra.Command) error { - return cmd.GenBashCompletion(out) -} - -func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { - zshInitialization := `#compdef hcloud - -__hcloud_bash_source() { - alias shopt=':' - alias _expand=_bash_expand - alias _complete=_bash_comp - emulate -L sh - setopt kshglob noshglob braceexpand - source "$@" -} -__hcloud_type() { - # -t is not supported by zsh - if [ "$1" == "-t" ]; then - shift - # fake Bash 4 to disable "complete -o nospace". Instead - # "compopt +-o nospace" is used in the code to toggle trailing - # spaces. We don't support that, but leave trailing spaces on - # all the time - if [ "$1" = "__hcloud_compopt" ]; then - echo builtin - return 0 - fi - fi - type "$@" -} -__hcloud_compgen() { - local completions w - completions=( $(compgen "$@") ) || return $? - # filter by given word as prefix - while [[ "$1" = -* && "$1" != -- ]]; do - shift - shift - done - if [[ "$1" == -- ]]; then - shift - fi - for w in "${completions[@]}"; do - if [[ "${w}" = "$1"* ]]; then - echo "${w}" - fi - done -} -__hcloud_compopt() { - true # don't do anything. Not supported by bashcompinit in zsh -} -__hcloud_declare() { - if [ "$1" == "-F" ]; then - whence -w "$@" - else - builtin declare "$@" - fi -} -__hcloud_ltrim_colon_completions() -{ - if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then - # Remove colon-word prefix from COMPREPLY items - local colon_word=${1%${1##*:}} - local i=${#COMPREPLY[*]} - while [[ $((--i)) -ge 0 ]]; do - COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} - done - fi -} -__hcloud_get_comp_words_by_ref() { - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[${COMP_CWORD}-1]}" - words=("${COMP_WORDS[@]}") - cword=("${COMP_CWORD[@]}") -} -__hcloud_filedir() { - local RET OLD_IFS w qw - __debug "_filedir $@ cur=$cur" - if [[ "$1" = \~* ]]; then - # somehow does not work. Maybe, zsh does not call this at all - eval echo "$1" - return 0 - fi - OLD_IFS="$IFS" - IFS=$'\n' - if [ "$1" = "-d" ]; then - shift - RET=( $(compgen -d) ) - else - RET=( $(compgen -f) ) - fi - IFS="$OLD_IFS" - IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" - for w in ${RET[@]}; do - if [[ ! "${w}" = "${cur}"* ]]; then - continue - fi - if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then - qw="$(__hcloud_quote "${w}")" - if [ -d "${w}" ]; then - COMPREPLY+=("${qw}/") - else - COMPREPLY+=("${qw}") - fi - fi - done -} -__hcloud_quote() { - if [[ $1 == \'* || $1 == \"* ]]; then - # Leave out first character - printf %q "${1:1}" - else - printf %q "$1" - fi -} -autoload -U +X bashcompinit && bashcompinit -# use word boundary patterns for BSD or GNU sed -LWORD='[[:<:]]' -RWORD='[[:>:]]' -if sed --help 2>&1 | grep -q GNU; then - LWORD='\<' - RWORD='\>' -fi -__hcloud_convert_bash_to_zsh() { - sed \ - -e 's/declare -F/whence -w/' \ - -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ - -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ - -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ - -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ - -e "s/${LWORD}_filedir${RWORD}/__hcloud_filedir/g" \ - -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__hcloud_get_comp_words_by_ref/g" \ - -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__hcloud_ltrim_colon_completions/g" \ - -e "s/${LWORD}compgen${RWORD}/__hcloud_compgen/g" \ - -e "s/${LWORD}compopt${RWORD}/__hcloud_compopt/g" \ - -e "s/${LWORD}declare${RWORD}/__hcloud_declare/g" \ - -e "s/\\\$(type${RWORD}/\$(__hcloud_type/g" \ - <<'BASH_COMPLETION_EOF' -` - out.Write([]byte(zshInitialization)) - - buf := new(bytes.Buffer) - cmd.Root().GenBashCompletion(buf) - out.Write(buf.Bytes()) - - zshTail := ` -BASH_COMPLETION_EOF -} -__hcloud_bash_source <(__hcloud_convert_bash_to_zsh) -` - out.Write([]byte(zshTail)) - return nil -} diff --git a/cli/config.go b/cli/config.go index 7c21294b..d226ebb9 100644 --- a/cli/config.go +++ b/cli/config.go @@ -19,6 +19,17 @@ type ConfigContext struct { Token string } +func (config *Config) ContextNames() []string { + if len(config.Contexts) == 0 { + return nil + } + names := make([]string, len(config.Contexts)) + for i, ctx := range config.Contexts { + names[i] = ctx.Name + } + return names +} + func (config *Config) ContextByName(name string) *ConfigContext { for _, c := range config.Contexts { if c.Name == name { diff --git a/cli/context.go b/cli/context.go index 99283f90..c4c9094b 100644 --- a/cli/context.go +++ b/cli/context.go @@ -9,7 +9,6 @@ func newContextCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runContext), } cmd.AddCommand( newContextCreateCommand(cli), @@ -20,7 +19,3 @@ func newContextCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runContext(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/context_delete.go b/cli/context_delete.go index f1d2db2f..2fcb66d1 100644 --- a/cli/context_delete.go +++ b/cli/context_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newContextDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] NAME", Short: "Delete a context", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.Config.ContextNames)), TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextDelete), diff --git a/cli/context_use.go b/cli/context_use.go index c98471e5..94504466 100644 --- a/cli/context_use.go +++ b/cli/context_use.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newContextUseCommand(cli *CLI) *cobra.Command { Use: "use [FLAGS] NAME", Short: "Use a context", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.Config.ContextNames)), TraverseChildren: true, DisableFlagsInUseLine: true, RunE: cli.wrap(runContextUse), diff --git a/cli/datacenter.go b/cli/datacenter.go index b8b5211d..67ed1f6f 100644 --- a/cli/datacenter.go +++ b/cli/datacenter.go @@ -9,7 +9,6 @@ func newDatacenterCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runDatacenter), } cmd.AddCommand( newDatacenterListCommand(cli), @@ -17,7 +16,3 @@ func newDatacenterCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runDatacenter(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/datacenter_describe.go b/cli/datacenter_describe.go index be6ba3a2..cfe91dcb 100644 --- a/cli/datacenter_describe.go +++ b/cli/datacenter_describe.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newDatacenterDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] DATACENTER", Short: "Describe a datacenter", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.DataCenterNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip.go b/cli/floatingip.go index 5c8e2241..262d6d7c 100644 --- a/cli/floatingip.go +++ b/cli/floatingip.go @@ -9,7 +9,6 @@ func newFloatingIPCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runFloatingIP), } cmd.AddCommand( newFloatingIPUpdateCommand(cli), @@ -27,7 +26,3 @@ func newFloatingIPCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runFloatingIP(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/floatingip_add_label.go b/cli/floatingip_add_label.go index e3cb0f37..60681618 100644 --- a/cli/floatingip_add_label.go +++ b/cli/floatingip_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newFloatingIPAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] FLOATINGIP LABEL", Short: "Add a label to a Floating IP", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateFloatingIPAddLabel, cli.ensureToken), diff --git a/cli/floatingip_assign.go b/cli/floatingip_assign.go index bdeef52c..4b54beda 100644 --- a/cli/floatingip_assign.go +++ b/cli/floatingip_assign.go @@ -3,14 +3,19 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) func newFloatingIPAssignCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "assign [FLAGS] FLOATINGIP SERVER", - Short: "Assign a Floating IP to a server", - Args: cobra.ExactArgs(2), + Use: "assign [FLAGS] FLOATINGIP SERVER", + Short: "Assign a Floating IP to a server", + Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.FloatingIPNames), + cmpl.SuggestCandidatesF(cli.ServerNames), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_create.go b/cli/floatingip_create.go index 6cd0c1da..80008c8b 100644 --- a/cli/floatingip_create.go +++ b/cli/floatingip_create.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -19,22 +20,18 @@ func newFloatingIPCreateCommand(cli *CLI) *cobra.Command { RunE: cli.wrap(runFloatingIPCreate), } cmd.Flags().String("type", "", "Type (ipv4 or ipv6) (required)") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_floatingip_types"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("ipv4", "ipv6")) cmd.MarkFlagRequired("type") cmd.Flags().String("description", "", "Description") + cmd.Flags().String("name", "", "Name") + cmd.Flags().String("home-location", "", "Home location") - cmd.Flag("home-location").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_location_names"}, - } + cmd.RegisterFlagCompletionFunc("home-location", cmpl.SuggestCandidatesF(cli.LocationNames)) cmd.Flags().String("server", "", "Server to assign Floating IP to") - cmd.Flag("server").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_server_names"}, - } + cmd.RegisterFlagCompletionFunc("server", cmpl.SuggestCandidatesF(cli.ServerNames)) cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)") diff --git a/cli/floatingip_delete.go b/cli/floatingip_delete.go index 980adc23..5f0a3ea1 100644 --- a/cli/floatingip_delete.go +++ b/cli/floatingip_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newFloatingIPDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] FLOATINGIP", Short: "Delete a Floating IP", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_describe.go b/cli/floatingip_describe.go index 6cbbd168..ce0daa40 100644 --- a/cli/floatingip_describe.go +++ b/cli/floatingip_describe.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dustin/go-humanize" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -14,6 +15,7 @@ func newFloatingIPDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] FLOATINGIP", Short: "Describe a Floating IP", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_disable_protection.go b/cli/floatingip_disable_protection.go index ebb6d957..8959bee9 100644 --- a/cli/floatingip_disable_protection.go +++ b/cli/floatingip_disable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for a Floating IP", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for a Floating IP", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.FloatingIPNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_enable_protection.go b/cli/floatingip_enable_protection.go index d9ef00c1..63625b21 100644 --- a/cli/floatingip_enable_protection.go +++ b/cli/floatingip_enable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for a Floating IP", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] FLOATINGIP PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for a Floating IP", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.FloatingIPNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_list.go b/cli/floatingip_list.go index 1afd88ce..6fe3ad06 100644 --- a/cli/floatingip_list.go +++ b/cli/floatingip_list.go @@ -118,7 +118,7 @@ func describeFloatingIPListTableOutput(cli *CLI) *tableOutput { floatingIP := obj.(*hcloud.FloatingIP) var server string if floatingIP.Server != nil && cli != nil { - return cli.GetServerName(floatingIP.Server.ID) + return cli.ServerName(floatingIP.Server.ID) } return na(server) })). diff --git a/cli/floatingip_remove_label.go b/cli/floatingip_remove_label.go index 66d91df2..b714d1c0 100644 --- a/cli/floatingip_remove_label.go +++ b/cli/floatingip_remove_label.go @@ -4,15 +4,24 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newFloatingIPRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] FLOATINGIP LABELKEY", - Short: "Remove a label from a Floating IP", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] FLOATINGIP LABELKEY", + Short: "Remove a label from a Floating IP", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.FloatingIPNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.FloatingIPLabelKeys(args[0]) + })), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateFloatingIPRemoveLabel, cli.ensureToken), diff --git a/cli/floatingip_set_rdns.go b/cli/floatingip_set_rdns.go index 4c3c111b..482ddce0 100644 --- a/cli/floatingip_set_rdns.go +++ b/cli/floatingip_set_rdns.go @@ -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" ) @@ -12,6 +13,7 @@ func newFloatingIPSetRDNSCommand(cli *CLI) *cobra.Command { Use: "set-rdns [FLAGS] FLOATINGIP", Short: "Change reverse DNS of a Floating IP", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_unassign.go b/cli/floatingip_unassign.go index 68c1779a..c40cb444 100644 --- a/cli/floatingip_unassign.go +++ b/cli/floatingip_unassign.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newFloatingIPUnassignCommand(cli *CLI) *cobra.Command { Use: "unassign [FLAGS] FLOATINGIP", Short: "Unassign a Floating IP", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/floatingip_update.go b/cli/floatingip_update.go index 4a579b82..92f513c6 100644 --- a/cli/floatingip_update.go +++ b/cli/floatingip_update.go @@ -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" ) @@ -12,6 +13,7 @@ func newFloatingIPUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] FLOATINGIP", Short: "Update a Floating IP", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.FloatingIPNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/image.go b/cli/image.go index 120dc012..5ba81cea 100644 --- a/cli/image.go +++ b/cli/image.go @@ -9,7 +9,6 @@ func newImageCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runImage), } cmd.AddCommand( newImageListCommand(cli), @@ -23,7 +22,3 @@ func newImageCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runImage(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/image_add_label.go b/cli/image_add_label.go index adf421ad..017b54d0 100644 --- a/cli/image_add_label.go +++ b/cli/image_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newImageAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] IMAGE LABEL", Short: "Add a label to an image", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ImageNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateImageAddLabel, cli.ensureToken), diff --git a/cli/image_delete.go b/cli/image_delete.go index 9ac6c2c7..25b47101 100644 --- a/cli/image_delete.go +++ b/cli/image_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newImageDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] IMAGE", Short: "Delete an image", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ImageNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/image_describe.go b/cli/image_describe.go index df3203da..55d98bf6 100644 --- a/cli/image_describe.go +++ b/cli/image_describe.go @@ -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" ) @@ -14,6 +15,7 @@ func newImageDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] IMAGE", Short: "Describe an image", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ImageNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/image_disable_protection.go b/cli/image_disable_protection.go index 21528236..229d5abc 100644 --- a/cli/image_disable_protection.go +++ b/cli/image_disable_protection.go @@ -6,15 +6,20 @@ import ( "strconv" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for an image", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for an image", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ImageNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/image_enable_protection.go b/cli/image_enable_protection.go index 730035ea..9f163e44 100644 --- a/cli/image_enable_protection.go +++ b/cli/image_enable_protection.go @@ -6,15 +6,20 @@ import ( "strconv" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newImageEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for an image", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] IMAGE PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for an image", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ImageNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/image_list.go b/cli/image_list.go index 0cfe01b6..853d1b00 100644 --- a/cli/image_list.go +++ b/cli/image_list.go @@ -122,14 +122,14 @@ func describeImageListTableOutput(cli *CLI) *tableOutput { AddFieldOutputFn("bound_to", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) if image.BoundTo != nil && cli != nil { - return cli.GetServerName(image.BoundTo.ID) + return cli.ServerName(image.BoundTo.ID) } return na("") })). AddFieldOutputFn("created_from", fieldOutputFn(func(obj interface{}) string { image := obj.(*hcloud.Image) if image.CreatedFrom != nil && cli != nil { - return cli.GetServerName(image.CreatedFrom.ID) + return cli.ServerName(image.CreatedFrom.ID) } return na("") })). diff --git a/cli/image_remove_label.go b/cli/image_remove_label.go index 619b1d39..84c50336 100644 --- a/cli/image_remove_label.go +++ b/cli/image_remove_label.go @@ -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 newImageRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] IMAGE LABELKEY", - Short: "Remove a label from an image", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] IMAGE LABELKEY", + Short: "Remove a label from an image", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ImageNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.ImageLabelKeys(args[0]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateImageRemoveLabel, cli.ensureToken), diff --git a/cli/image_update.go b/cli/image_update.go index fef07103..1005d5e5 100644 --- a/cli/image_update.go +++ b/cli/image_update.go @@ -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" ) @@ -12,6 +13,7 @@ func newImageUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] IMAGE", Short: "Update an image", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ImageNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -21,9 +23,7 @@ func newImageUpdateCommand(cli *CLI) *cobra.Command { cmd.Flags().String("description", "", "Image description") cmd.Flags().String("type", "", "Image type") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_image_types_no_system"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("backup", "snapshot")) return cmd } diff --git a/cli/iso.go b/cli/iso.go index a653a1e5..df26c2c4 100644 --- a/cli/iso.go +++ b/cli/iso.go @@ -9,7 +9,6 @@ func newISOCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runServer), } cmd.AddCommand( newISOListCommand(cli), @@ -17,7 +16,3 @@ func newISOCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runISO(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/iso_describe.go b/cli/iso_describe.go index 05c52d30..e9bd2784 100644 --- a/cli/iso_describe.go +++ b/cli/iso_describe.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newISODescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] ISO", Short: "Describe an ISO", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ISONames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer.go b/cli/load_balancer.go index 344c8389..3fc40658 100644 --- a/cli/load_balancer.go +++ b/cli/load_balancer.go @@ -9,7 +9,6 @@ func newLoadBalancerCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runLoadBalancer), } cmd.AddCommand( newLoadBalancerCreateCommand(cli), @@ -35,7 +34,3 @@ func newLoadBalancerCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runLoadBalancer(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/load_balancer_add_label.go b/cli/load_balancer_add_label.go index 6101591f..209b13c8 100644 --- a/cli/load_balancer_add_label.go +++ b/cli/load_balancer_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newLoadBalancerAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] LOADBALANCER LABEL", Short: "Add a label to a Load Balancer", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateLoadBalancerAddLabel, cli.ensureToken), diff --git a/cli/load_balancer_add_service.go b/cli/load_balancer_add_service.go index ac5b9453..f6ceeed9 100644 --- a/cli/load_balancer_add_service.go +++ b/cli/load_balancer_add_service.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -11,6 +13,7 @@ func newLoadBalancerAddServiceCommand(cli *CLI) *cobra.Command { Use: "add-service LOADBALANCER FLAGS", Short: "Add a service from a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateLoadBalancerAddService, cli.ensureToken), diff --git a/cli/load_balancer_add_target.go b/cli/load_balancer_add_target.go index 45872dd0..142d418a 100644 --- a/cli/load_balancer_add_target.go +++ b/cli/load_balancer_add_target.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLoadBalancerAddTargetCommand(cli *CLI) *cobra.Command { Use: "add-target LOADBALANCER FLAGS", Short: "Add a target to a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -20,10 +22,10 @@ func newLoadBalancerAddTargetCommand(cli *CLI) *cobra.Command { } cmd.Flags().String("server", "", "Name or ID of the server") - cmd.Flag("server").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_server_names"}, - } + cmd.RegisterFlagCompletionFunc("server", cmpl.SuggestCandidatesF(cli.ServerNames)) + cmd.Flags().String("label-selector", "", "Label Selector") + cmd.Flags().Bool("use-private-ip", false, "Determine if the Load Balancer should connect to the target via the network") cmd.Flags().String("ip", "", "Use the passed IP address as target") return cmd diff --git a/cli/load_balancer_attach_to_network.go b/cli/load_balancer_attach_to_network.go index 4edb25c7..36c05d60 100644 --- a/cli/load_balancer_attach_to_network.go +++ b/cli/load_balancer_attach_to_network.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -11,6 +13,7 @@ func newLoadBalancerAttachToNetworkCommand(cli *CLI) *cobra.Command { Use: "attach-to-network [FLAGS] LOADBALANCER", Short: "Attach a Load Balancer to a Network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -18,9 +21,7 @@ func newLoadBalancerAttachToNetworkCommand(cli *CLI) *cobra.Command { } cmd.Flags().StringP("network", "n", "", "Network (ID or name) (required)") - cmd.Flag("network").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_names"}, - } + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) cmd.MarkFlagRequired("network") cmd.Flags().IP("ip", nil, "IP address to assign to the Load Balancer (auto-assigned if omitted)") diff --git a/cli/load_balancer_change_algorithm.go b/cli/load_balancer_change_algorithm.go index 8f1cff37..187baa6f 100644 --- a/cli/load_balancer_change_algorithm.go +++ b/cli/load_balancer_change_algorithm.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -11,6 +13,7 @@ func newLoadBalancerChangeAlgorithmCommand(cli *CLI) *cobra.Command { Use: "change-algorithm LOADBALANCER FLAGS", Short: "Changes the algorithm of a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -18,10 +21,12 @@ func newLoadBalancerChangeAlgorithmCommand(cli *CLI) *cobra.Command { } cmd.Flags().String("algorithm-type", "", "The new algorithm of the Load Balancer") - cmd.Flag("algorithm-type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_load_balancer_algorithm_types"}, - } + cmd.RegisterFlagCompletionFunc("algorithm-type", cmpl.SuggestCandidates( + string(hcloud.LoadBalancerAlgorithmTypeRoundRobin), + string(hcloud.LoadBalancerAlgorithmTypeRoundRobin), + )) cmd.MarkFlagRequired("algorithm-type") + return cmd } diff --git a/cli/load_balancer_change_type.go b/cli/load_balancer_change_type.go index 62a554e5..b03b7f8b 100644 --- a/cli/load_balancer_change_type.go +++ b/cli/load_balancer_change_type.go @@ -3,15 +3,20 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newLoadBalancerChangeTypeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "change-type [FLAGS] LOADBALANCER LOADBALANCERTYPE", - Short: "Change type of a Load Balancer", - Args: cobra.ExactArgs(2), + Use: "change-type [FLAGS] LOADBALANCER LOADBALANCERTYPE", + Short: "Change type of a Load Balancer", + Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.LoadBalancerNames), + cmpl.SuggestCandidatesF(cli.LoadBalancerTypeNames), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_create.go b/cli/load_balancer_create.go index d459cbd6..e41bcedb 100644 --- a/cli/load_balancer_create.go +++ b/cli/load_balancer_create.go @@ -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" ) @@ -22,25 +23,19 @@ func newLoadBalancerCreateCommand(cli *CLI) *cobra.Command { cmd.MarkFlagRequired("name") cmd.Flags().String("type", "", "Load Balancer type (ID or name) (required)") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_load_balancer_type_names"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidatesF(cli.LoadBalancerTypeNames)) cmd.MarkFlagRequired("type") cmd.Flags().String("algorithm-type", "", "Algorithm Type name (round_robin or least_connections)") - - cmd.Flag("algorithm-type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_load_balancer_algorithm_types"}, - } + cmd.RegisterFlagCompletionFunc("algorithm-type", cmpl.SuggestCandidates( + string(hcloud.LoadBalancerAlgorithmTypeLeastConnections), + string(hcloud.LoadBalancerAlgorithmTypeRoundRobin), + )) cmd.Flags().String("location", "", "Location (ID or name)") - cmd.Flag("location").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_location_names"}, - } + cmd.RegisterFlagCompletionFunc("location", cmpl.SuggestCandidatesF(cli.LocationNames)) cmd.Flags().String("network-zone", "", "Network Zone") - cmd.Flag("network-zone").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_zones"}, - } + cmd.RegisterFlagCompletionFunc("network-zone", cmpl.SuggestCandidates("eu-central")) cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)") diff --git a/cli/load_balancer_delete.go b/cli/load_balancer_delete.go index 3a10395a..ccba6461 100644 --- a/cli/load_balancer_delete.go +++ b/cli/load_balancer_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newLoadBalancerDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] LOADBALANCER", Short: "Delete a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_delete_service.go b/cli/load_balancer_delete_service.go index db1418c0..1a5601a8 100644 --- a/cli/load_balancer_delete_service.go +++ b/cli/load_balancer_delete_service.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -10,6 +11,7 @@ func newLoadBalancerDeleteServiceCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ Use: "delete-service [FLAGS] LOADBALANCER", Short: "Deletes a service from a Load Balancer", + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), Args: cobra.RangeArgs(1, 2), TraverseChildren: true, DisableFlagsInUseLine: true, diff --git a/cli/load_balancer_describe.go b/cli/load_balancer_describe.go index bc8303b2..3436591e 100644 --- a/cli/load_balancer_describe.go +++ b/cli/load_balancer_describe.go @@ -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" ) @@ -14,6 +15,7 @@ func newLoadBalancerDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] LOADBALANCER", Short: "Describe a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -128,7 +130,7 @@ func loadBalancerDescribeText(cli *CLI, loadBalancer *hcloud.LoadBalancer, withL case hcloud.LoadBalancerTargetTypeServer: fmt.Printf(" Server:\n") fmt.Printf(" ID:\t\t\t%d\n", target.Server.Server.ID) - fmt.Printf(" Name:\t\t\t%s\n", cli.GetServerName(target.Server.Server.ID)) + fmt.Printf(" Name:\t\t\t%s\n", cli.ServerName(target.Server.Server.ID)) fmt.Printf(" Use Private IP:\t\t%s\n", yesno(target.UsePrivateIP)) fmt.Printf(" Status:\n") for _, healthStatus := range target.HealthStatus { diff --git a/cli/load_balancer_detach_from_network.go b/cli/load_balancer_detach_from_network.go index 808462ca..b1e2534f 100644 --- a/cli/load_balancer_detach_from_network.go +++ b/cli/load_balancer_detach_from_network.go @@ -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" @@ -13,15 +14,14 @@ func newLoadBalancerDetachFromNetworkCommand(cli *CLI) *cobra.Command { Use: "detach-from-network [FLAGS] LOADBALANCER", Short: "Detach a Load Balancer from a Network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runLoadBalancerDetachFromNetwork), } cmd.Flags().StringP("network", "n", "", "Network (ID or name) (required)") - cmd.Flag("network").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_names"}, - } + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) cmd.MarkFlagRequired("network") return cmd } diff --git a/cli/load_balancer_disable_protection.go b/cli/load_balancer_disable_protection.go index e63cee6f..d811ba7e 100644 --- a/cli/load_balancer_disable_protection.go +++ b/cli/load_balancer_disable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newLoadBalancerDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] LOADBALANCER PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for a Load Balancer", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] LOADBALANCER PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for a Load Balancer", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.LoadBalancerNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_disable_public_interface.go b/cli/load_balancer_disable_public_interface.go index 6fa0c3c2..fd6a7f1b 100644 --- a/cli/load_balancer_disable_public_interface.go +++ b/cli/load_balancer_disable_public_interface.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newLoadBalancerDisablePublicInterface(cli *CLI) *cobra.Command { Use: "disable-public-interface [FLAGS] LOADBALANCER", Short: "Disable the public interface of a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_enable_protection.go b/cli/load_balancer_enable_protection.go index 81383c04..4e763d6b 100644 --- a/cli/load_balancer_enable_protection.go +++ b/cli/load_balancer_enable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newLoadBalancerEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] LOADBALANCER PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for a Load Balancer", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] LOADBALANCER PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for a Load Balancer", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.LoadBalancerNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_enable_public_interface.go b/cli/load_balancer_enable_public_interface.go index 254338b9..f8809a3a 100644 --- a/cli/load_balancer_enable_public_interface.go +++ b/cli/load_balancer_enable_public_interface.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newLoadBalancerEnablePublicInterface(cli *CLI) *cobra.Command { Use: "enable-public-interface [FLAGS] LOADBALANCER", Short: "Enable the public interface of a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_remove_label.go b/cli/load_balancer_remove_label.go index 1ae105f5..70278a09 100644 --- a/cli/load_balancer_remove_label.go +++ b/cli/load_balancer_remove_label.go @@ -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 newLoadBalancerRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] LOADBALANCER LABELKEY", - Short: "Remove a label from a Load Balancer", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] LOADBALANCER LABELKEY", + Short: "Remove a label from a Load Balancer", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.LoadBalancerNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.LoadBalancerLabelKeys(args[0]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateLoadBalancerRemoveLabel, cli.ensureToken), diff --git a/cli/load_balancer_remove_target.go b/cli/load_balancer_remove_target.go index 258970f0..b02b48f6 100644 --- a/cli/load_balancer_remove_target.go +++ b/cli/load_balancer_remove_target.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLoadBalancerRemoveTargetCommand(cli *CLI) *cobra.Command { Use: "remove-target LOADBALANCER FLAGS", Short: "Remove a target to a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -20,11 +22,12 @@ func newLoadBalancerRemoveTargetCommand(cli *CLI) *cobra.Command { } cmd.Flags().String("server", "", "Name or ID of the server") - cmd.Flag("server").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_server_names"}, - } + cmd.RegisterFlagCompletionFunc("server", cmpl.SuggestCandidatesF(cli.ServerNames)) + cmd.Flags().String("label-selector", "", "Label Selector") + cmd.Flags().String("ip", "", "IP address of an IP target") + return cmd } diff --git a/cli/load_balancer_type.go b/cli/load_balancer_type.go index afb40bc9..126b773d 100644 --- a/cli/load_balancer_type.go +++ b/cli/load_balancer_type.go @@ -9,7 +9,6 @@ func newLoadBalancerTypeCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runLoadBalancerType), } cmd.AddCommand( newLoadBalancerTypenDescribeCommand(cli), @@ -17,7 +16,3 @@ func newLoadBalancerTypeCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runLoadBalancerType(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/load_balancer_type_describe.go b/cli/load_balancer_type_describe.go index e836002d..9279e557 100644 --- a/cli/load_balancer_type_describe.go +++ b/cli/load_balancer_type_describe.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLoadBalancerTypenDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] LOADBALANCERTYPE", Short: "Describe a Load Balancer type", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerTypeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_update.go b/cli/load_balancer_update.go index 7600d450..58f2b20f 100644 --- a/cli/load_balancer_update.go +++ b/cli/load_balancer_update.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLoadBalancerUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] LOADBALANCER", Short: "Update a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/load_balancer_update_service.go b/cli/load_balancer_update_service.go index b3b543e0..ec6efca2 100644 --- a/cli/load_balancer_update_service.go +++ b/cli/load_balancer_update_service.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLoadBalancerUpdateServiceCommand(cli *CLI) *cobra.Command { Use: "update-service LOADBALANCER FLAGS", Short: "Updates a service from a Load Balancer", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LoadBalancerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/location.go b/cli/location.go index 7fc15535..a2a6a9d7 100644 --- a/cli/location.go +++ b/cli/location.go @@ -9,7 +9,6 @@ func newLocationCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runServer), } cmd.AddCommand( newLocationListCommand(cli), @@ -17,7 +16,3 @@ func newLocationCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runLocation(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/location_describe.go b/cli/location_describe.go index f834c5dc..a354e84b 100644 --- a/cli/location_describe.go +++ b/cli/location_describe.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newLocationDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] LOCATION", Short: "Describe a location", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.LocationNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network.go b/cli/network.go index 7d81a3ab..e6d50c7d 100644 --- a/cli/network.go +++ b/cli/network.go @@ -9,7 +9,6 @@ func newNetworkCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runNetwork), } cmd.AddCommand( newNetworkListCommand(cli), @@ -29,7 +28,3 @@ func newNetworkCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runNetwork(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/network_add_label.go b/cli/network_add_label.go index f2f480a1..9f9ccda3 100644 --- a/cli/network_add_label.go +++ b/cli/network_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newNetworkAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] NETWORK LABEL", Short: "Add a label to a network", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateNetworkAddLabel, cli.ensureToken), diff --git a/cli/network_add_route.go b/cli/network_add_route.go index fb0bf89a..4ff8c144 100644 --- a/cli/network_add_route.go +++ b/cli/network_add_route.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkAddRouteCommand(cli *CLI) *cobra.Command { Use: "add-route NETWORK FLAGS", Short: "Add a route to a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_add_subnet.go b/cli/network_add_subnet.go index 247581fa..04f333d6 100644 --- a/cli/network_add_subnet.go +++ b/cli/network_add_subnet.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkAddSubnetCommand(cli *CLI) *cobra.Command { Use: "add-subnet NETWORK FLAGS", Short: "Add a subnet to a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -20,15 +22,11 @@ func newNetworkAddSubnetCommand(cli *CLI) *cobra.Command { } cmd.Flags().String("type", "", "Type of subnet") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_subnet_types"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("cloud", "server")) cmd.MarkFlagRequired("type") cmd.Flags().String("network-zone", "", "Name of network zone") - cmd.Flag("network-zone").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_zones"}, - } + cmd.RegisterFlagCompletionFunc("network-zone", cmpl.SuggestCandidates("eu-central")) cmd.MarkFlagRequired("network-zone") cmd.Flags().IPNet("ip-range", net.IPNet{}, "Range to allocate IPs from") diff --git a/cli/network_change_ip_range.go b/cli/network_change_ip_range.go index 3b7ec764..9200366e 100644 --- a/cli/network_change_ip_range.go +++ b/cli/network_change_ip_range.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkChangeIPRangeCommand(cli *CLI) *cobra.Command { Use: "change-ip-range [FLAGS] NETWORK", Short: "Change the IP range of a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_delete.go b/cli/network_delete.go index 5ecbf34e..0bd23452 100644 --- a/cli/network_delete.go +++ b/cli/network_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newNetworkDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] NETWORK", Short: "Delete a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_describe.go b/cli/network_describe.go index d98d715b..6184ca09 100644 --- a/cli/network_describe.go +++ b/cli/network_describe.go @@ -4,6 +4,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" ) @@ -13,6 +14,7 @@ func newNetworkDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] NETWORK", Short: "Describe a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_disable_protection.go b/cli/network_disable_protection.go index 06e20123..49cf99b9 100644 --- a/cli/network_disable_protection.go +++ b/cli/network_disable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for a network", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for a network", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.NetworkNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_enable_protection.go b/cli/network_enable_protection.go index ec9eda93..ba5ca818 100644 --- a/cli/network_enable_protection.go +++ b/cli/network_enable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newNetworkEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for a network", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] NETWORK PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for a network", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.NetworkNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_remove_label.go b/cli/network_remove_label.go index 62ebc0b3..0deb9750 100644 --- a/cli/network_remove_label.go +++ b/cli/network_remove_label.go @@ -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 newNetworkRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] NETWORK LABELKEY", - Short: "Remove a label from a network", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] NETWORK LABELKEY", + Short: "Remove a label from a network", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.NetworkNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.NetworkLabelKeys(args[0]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateNetworkRemoveLabel, cli.ensureToken), diff --git a/cli/network_remove_route.go b/cli/network_remove_route.go index e45ce117..bee52877 100644 --- a/cli/network_remove_route.go +++ b/cli/network_remove_route.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkRemoveRouteCommand(cli *CLI) *cobra.Command { Use: "remove-route NETWORK FLAGS", Short: "Remove a route from a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_remove_subnet.go b/cli/network_remove_subnet.go index 66ee2423..c9806297 100644 --- a/cli/network_remove_subnet.go +++ b/cli/network_remove_subnet.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkRemoveSubnetCommand(cli *CLI) *cobra.Command { Use: "remove-subnet NETWORK FLAGS", Short: "Remove a subnet from a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/network_update.go b/cli/network_update.go index 774dd30d..16a68fca 100644 --- a/cli/network_update.go +++ b/cli/network_update.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newNetworkUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] NETWORK", Short: "Update a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.NetworkNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/output.go b/cli/output.go index 6ecf2e1a..895faba3 100644 --- a/cli/output.go +++ b/cli/output.go @@ -11,9 +11,12 @@ import ( "unicode" "github.com/fatih/structs" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) +const flagName = "output" + type outputOption struct { Name string Values []string @@ -36,20 +39,25 @@ func outputOptionColumns(columns []string) outputOption { } func addOutputFlag(cmd *cobra.Command, options ...outputOption) { - var names []string + var ( + names []string + values []string + ) for _, option := range options { + name := option.Name if option.Values != nil { - names = append(names, option.Name+"=...") - } else { - names = append(names, option.Name) + name += "=..." + values = append(values, option.Values...) } + names = append(names, name) } cmd.Flags().StringArrayP( - "output", + flagName, "o", []string{}, fmt.Sprintf("output options: %s", strings.Join(names, "|")), ) + cmd.RegisterFlagCompletionFunc(flagName, cmpl.SuggestCandidates(values...)) cmd.PreRunE = chainRunE(cmd.PreRunE, validateOutputFlag(options)) } @@ -69,7 +77,7 @@ func validateOutputFlag(options []outputOption) func(cmd *cobra.Command, args [] } } - flagValues, err := cmd.Flags().GetStringArray("output") + flagValues, err := cmd.Flags().GetStringArray(flagName) if err != nil { return err } @@ -92,7 +100,7 @@ func validateOutputFlag(options []outputOption) func(cmd *cobra.Command, args [] } func outputFlagsForCommand(cmd *cobra.Command) outputOpts { - opts, _ := cmd.Flags().GetStringArray("output") + opts, _ := cmd.Flags().GetStringArray(flagName) return parseOutputFlags(opts) } diff --git a/cli/root.go b/cli/root.go index 6fd32e59..5d453160 100644 --- a/cli/root.go +++ b/cli/root.go @@ -8,15 +8,13 @@ import ( func NewRootCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "hcloud", - Short: "Hetzner Cloud CLI", - Long: "A command-line interface for Hetzner Cloud", - RunE: cli.wrap(runRoot), - TraverseChildren: true, - SilenceUsage: true, - SilenceErrors: true, - DisableFlagsInUseLine: true, - BashCompletionFunction: bashCompletionFunc, + Use: "hcloud", + Short: "Hetzner Cloud CLI", + Long: "A command-line interface for Hetzner Cloud", + TraverseChildren: true, + SilenceUsage: true, + SilenceErrors: true, + DisableFlagsInUseLine: true, } cmd.AddCommand( newFloatingIPCommand(cli), @@ -39,7 +37,3 @@ func NewRootCommand(cli *CLI) *cobra.Command { cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress") return cmd } - -func runRoot(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/server.go b/cli/server.go index 6b3891a4..2e15ed6e 100644 --- a/cli/server.go +++ b/cli/server.go @@ -9,7 +9,6 @@ func newServerCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runServer), } cmd.AddCommand( newServerListCommand(cli), @@ -46,7 +45,3 @@ func newServerCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runServer(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/server_add_label.go b/cli/server_add_label.go index 14de8606..fe118c18 100644 --- a/cli/server_add_label.go +++ b/cli/server_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newServerAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] SERVER LABEL", Short: "Add a label to a server", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateServerAddLabel, cli.ensureToken), diff --git a/cli/server_attach_iso.go b/cli/server_attach_iso.go index 110d14f9..d723e0cb 100644 --- a/cli/server_attach_iso.go +++ b/cli/server_attach_iso.go @@ -3,15 +3,20 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) func newServerAttachISOCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "attach-iso [FLAGS] SERVER ISO", - Short: "Attach an ISO to a server", - Args: cobra.ExactArgs(2), - TraverseChildren: true, + Use: "attach-iso [FLAGS] SERVER ISO", + Short: "Attach an ISO to a server", + Args: cobra.ExactArgs(2), + TraverseChildren: true, + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ServerNames), + cmpl.SuggestCandidatesF(cli.ISONames), + ), DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerAttachISO), diff --git a/cli/server_attach_to_network.go b/cli/server_attach_to_network.go index 4bbe78f1..813d39a6 100644 --- a/cli/server_attach_to_network.go +++ b/cli/server_attach_to_network.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newServerAttachToNetworkCommand(cli *CLI) *cobra.Command { Use: "attach-to-network [FLAGS] SERVER", Short: "Attach a server to a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -20,9 +22,7 @@ func newServerAttachToNetworkCommand(cli *CLI) *cobra.Command { } cmd.Flags().StringP("network", "n", "", "Network (ID or name) (required)") - cmd.Flag("network").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_names"}, - } + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) cmd.MarkFlagRequired("network") cmd.Flags().IP("ip", nil, "IP address to assign to the server (auto-assigned if omitted)") diff --git a/cli/server_change_alias_ips.go b/cli/server_change_alias_ips.go index 38e130f2..3d40d5f8 100644 --- a/cli/server_change_alias_ips.go +++ b/cli/server_change_alias_ips.go @@ -4,6 +4,7 @@ import ( "fmt" "net" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newServerChangeAliasIPsCommand(cli *CLI) *cobra.Command { Use: "change-alias-ips [FLAGS] SERVER", Short: "Change a server's alias IPs in a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -20,9 +22,7 @@ func newServerChangeAliasIPsCommand(cli *CLI) *cobra.Command { } cmd.Flags().StringP("network", "n", "", "Network (ID or name) (required)") - cmd.Flag("network").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_names"}, - } + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) cmd.MarkFlagRequired("network") cmd.Flags().StringSlice("alias-ips", nil, "New alias IPs") diff --git a/cli/server_change_type.go b/cli/server_change_type.go index 9462feac..3e9b84d3 100644 --- a/cli/server_change_type.go +++ b/cli/server_change_type.go @@ -3,15 +3,20 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerChangeTypeCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "change-type [FLAGS] SERVER SERVERTYPE", - Short: "Change type of a server", - Args: cobra.ExactArgs(2), + Use: "change-type [FLAGS] SERVER SERVERTYPE", + Short: "Change type of a server", + Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ServerNames), + cmpl.SuggestCandidatesF(cli.ServerTypeNames), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_create.go b/cli/server_create.go index fcd7e711..52aeb595 100644 --- a/cli/server_create.go +++ b/cli/server_create.go @@ -10,6 +10,7 @@ import ( "os" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -30,31 +31,21 @@ func newServerCreateCommand(cli *CLI) *cobra.Command { cmd.MarkFlagRequired("name") cmd.Flags().String("type", "", "Server type (ID or name) (required)") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_servertype_names"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidatesF(cli.ServerTypeNames)) cmd.MarkFlagRequired("type") cmd.Flags().String("image", "", "Image (ID or name) (required)") - cmd.Flag("image").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_image_names"}, - } + cmd.RegisterFlagCompletionFunc("image", cmpl.SuggestCandidatesF(cli.ImageNames)) cmd.MarkFlagRequired("image") cmd.Flags().String("location", "", "Location (ID or name)") - cmd.Flag("location").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_location_names"}, - } + cmd.RegisterFlagCompletionFunc("location", cmpl.SuggestCandidatesF(cli.LocationNames)) cmd.Flags().String("datacenter", "", "Datacenter (ID or name)") - cmd.Flag("datacenter").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_datacenter_names"}, - } + cmd.RegisterFlagCompletionFunc("datacenter", cmpl.SuggestCandidatesF(cli.DataCenterNames)) cmd.Flags().StringSlice("ssh-key", nil, "ID or name of SSH key to inject (can be specified multiple times)") - cmd.Flag("ssh-key").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_sshkey_names"}, - } + cmd.RegisterFlagCompletionFunc("ssh-key", cmpl.SuggestCandidatesF(cli.SSHKeyNames)) cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)") @@ -63,10 +54,11 @@ func newServerCreateCommand(cli *CLI) *cobra.Command { cmd.Flags().Bool("start-after-create", true, "Start server right after creation") cmd.Flags().StringSlice("volume", nil, "ID or name of volume to attach (can be specified multiple times)") - cmd.Flag("volume").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_volume_names"}, - } + cmd.RegisterFlagCompletionFunc("volume", cmpl.SuggestCandidatesF(cli.VolumeNames)) + cmd.Flags().StringSlice("network", nil, "ID of network to attach the server to (can be specified multiple times)") + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) + cmd.Flags().Bool("automount", false, "Automount volumes after attach (default: false)") return cmd } diff --git a/cli/server_create_image.go b/cli/server_create_image.go index 1c47577b..69ba9884 100644 --- a/cli/server_create_image.go +++ b/cli/server_create_image.go @@ -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" ) @@ -18,9 +19,7 @@ func newServerCreateImageCommand(cli *CLI) *cobra.Command { RunE: cli.wrap(runServerCreateImage), } cmd.Flags().String("type", "", "Image type (required)") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_image_types_no_system"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("backup", "snapshot")) cmd.MarkFlagRequired("type") cmd.Flags().String("description", "", "Image description") diff --git a/cli/server_delete.go b/cli/server_delete.go index b8f34842..7100aeed 100644 --- a/cli/server_delete.go +++ b/cli/server_delete.go @@ -3,14 +3,16 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) func newServerDeleteCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "delete [FLAGS] SERVER", + Use: "delete SERVER", Short: "Delete a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_describe.go b/cli/server_describe.go index d7c3fd70..7b47222c 100644 --- a/cli/server_describe.go +++ b/cli/server_describe.go @@ -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" ) @@ -14,6 +15,7 @@ func newServerDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] SERVER", Short: "Describe a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_detach_from_network.go b/cli/server_detach_from_network.go index 4e55dd3a..c8b415f8 100644 --- a/cli/server_detach_from_network.go +++ b/cli/server_detach_from_network.go @@ -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" @@ -13,16 +14,16 @@ func newServerDetachFromNetworkCommand(cli *CLI) *cobra.Command { Use: "detach-from-network [FLAGS] SERVER", Short: "Detach a server from a network", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerDetachFromNetwork), } cmd.Flags().StringP("network", "n", "", "Network (ID or name) (required)") - cmd.Flag("network").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_network_names"}, - } + cmd.RegisterFlagCompletionFunc("network", cmpl.SuggestCandidatesF(cli.NetworkNames)) cmd.MarkFlagRequired("network") + return cmd } diff --git a/cli/server_detach_iso.go b/cli/server_detach_iso.go index a3be42e1..d3333cef 100644 --- a/cli/server_detach_iso.go +++ b/cli/server_detach_iso.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerDetachISOCommand(cli *CLI) *cobra.Command { Use: "detach-iso [FLAGS] SERVER", Short: "Detach an ISO from a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_disable_backup.go b/cli/server_disable_backup.go index 0414a332..86b5e9a5 100644 --- a/cli/server_disable_backup.go +++ b/cli/server_disable_backup.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerDisableBackupCommand(cli *CLI) *cobra.Command { Use: "disable-backup [FLAGS] SERVER", Short: "Disable backup for a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_disable_protection.go b/cli/server_disable_protection.go index 936e2130..1cfa2a73 100644 --- a/cli/server_disable_protection.go +++ b/cli/server_disable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for a server", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for a server", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ServerNames), + cmpl.SuggestCandidates("delete", "rebuild"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_disable_rescue.go b/cli/server_disable_rescue.go index f197eca1..b8a72342 100644 --- a/cli/server_disable_rescue.go +++ b/cli/server_disable_rescue.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerDisableRescueCommand(cli *CLI) *cobra.Command { Use: "disable-rescue [FLAGS] SERVER", Short: "Disable rescue for a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_enable_backup.go b/cli/server_enable_backup.go index 6e90a6c9..6222c894 100644 --- a/cli/server_enable_backup.go +++ b/cli/server_enable_backup.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerEnableBackupCommand(cli *CLI) *cobra.Command { Use: "enable-backup [FLAGS] SERVER", Short: "Enable backup for a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_enable_protection.go b/cli/server_enable_protection.go index 7fb16d31..13f293a3 100644 --- a/cli/server_enable_protection.go +++ b/cli/server_enable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newServerEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for a server", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] SERVER PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for a server", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ServerNames), + cmpl.SuggestCandidates("delete", "rebuild"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_enable_rescue.go b/cli/server_enable_rescue.go index cc5db973..068da97e 100644 --- a/cli/server_enable_rescue.go +++ b/cli/server_enable_rescue.go @@ -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" ) @@ -12,20 +13,17 @@ func newServerEnableRescueCommand(cli *CLI) *cobra.Command { Use: "enable-rescue [FLAGS] SERVER", Short: "Enable rescue for a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runServerEnableRescue), } cmd.Flags().String("type", "linux64", "Rescue type") - cmd.Flag("type").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_rescue_types"}, - } + cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("linux64", "linux32", "freebsd64")) cmd.Flags().StringSlice("ssh-key", nil, "ID or name of SSH key to inject (can be specified multiple times)") - cmd.Flag("ssh-key").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_sshkey_names"}, - } + cmd.RegisterFlagCompletionFunc("ssh-key", cmpl.SuggestCandidatesF(cli.SSHKeyNames)) return cmd } diff --git a/cli/server_ip.go b/cli/server_ip.go index 62d77182..2b375111 100644 --- a/cli/server_ip.go +++ b/cli/server_ip.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerIPCommand(cli *CLI) *cobra.Command { Use: "ip SERVER FLAGS", Short: "Print a server's IP address", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_list.go b/cli/server_list.go index 5fcf77fd..37023228 100644 --- a/cli/server_list.go +++ b/cli/server_list.go @@ -178,7 +178,7 @@ func describeServerListTableOutput(cli *CLI) *tableOutput { var networks []string if cli != nil { for _, network := range server.PrivateNet { - networks = append(networks, cli.GetNetworkName(network.Network.ID)) + networks = append(networks, cli.NetworkName(network.Network.ID)) } } return na(strings.Join(networks, ", ")) diff --git a/cli/server_poweroff.go b/cli/server_poweroff.go index d4f9fca5..162f28b8 100644 --- a/cli/server_poweroff.go +++ b/cli/server_poweroff.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerPoweroffCommand(cli *CLI) *cobra.Command { Use: "poweroff [FLAGS] SERVER", Short: "Poweroff a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_poweron.go b/cli/server_poweron.go index b3b6d347..85e9df8f 100644 --- a/cli/server_poweron.go +++ b/cli/server_poweron.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerPoweronCommand(cli *CLI) *cobra.Command { Use: "poweron [FLAGS] SERVER", Short: "Poweron a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_reboot.go b/cli/server_reboot.go index 8db7d33e..56a4a46f 100644 --- a/cli/server_reboot.go +++ b/cli/server_reboot.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerRebootCommand(cli *CLI) *cobra.Command { Use: "reboot [FLAGS] SERVER", Short: "Reboot a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_rebuild.go b/cli/server_rebuild.go index 47d5f97f..d331d3d1 100644 --- a/cli/server_rebuild.go +++ b/cli/server_rebuild.go @@ -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" ) @@ -12,6 +13,7 @@ func newServerRebuildCommand(cli *CLI) *cobra.Command { Use: "rebuild [FLAGS] SERVER", Short: "Rebuild a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, @@ -19,9 +21,7 @@ func newServerRebuildCommand(cli *CLI) *cobra.Command { } cmd.Flags().String("image", "", "ID or name of image to rebuild from (required)") - cmd.Flag("image").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_image_names"}, - } + cmd.RegisterFlagCompletionFunc("image", cmpl.SuggestCandidatesF(cli.ImageNames)) cmd.MarkFlagRequired("image") return cmd diff --git a/cli/server_remove_label.go b/cli/server_remove_label.go index af02ddd5..2f1a8779 100644 --- a/cli/server_remove_label.go +++ b/cli/server_remove_label.go @@ -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 newServerRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] SERVER LABELKEY", - Short: "Remove a label from a server", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] SERVER LABELKEY", + Short: "Remove a label from a server", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.ServerNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.ServerLabelKeys(args[1]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateServerRemoveLabel, cli.ensureToken), diff --git a/cli/server_request_console.go b/cli/server_request_console.go index dd976a4b..f3e153a7 100644 --- a/cli/server_request_console.go +++ b/cli/server_request_console.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerRequestConsoleCommand(cli *CLI) *cobra.Command { Use: "request-console [FLAGS] SERVER", Short: "Request a WebSocket VNC console for a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_reset.go b/cli/server_reset.go index 7548299f..0b1e0bc2 100644 --- a/cli/server_reset.go +++ b/cli/server_reset.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerResetCommand(cli *CLI) *cobra.Command { Use: "reset [FLAGS] SERVER", Short: "Reset a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_reset_password.go b/cli/server_reset_password.go index 4291d24b..238a094b 100644 --- a/cli/server_reset_password.go +++ b/cli/server_reset_password.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerResetPasswordCommand(cli *CLI) *cobra.Command { Use: "reset-password [FLAGS] SERVER", Short: "Reset the root password of a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_set_rdns.go b/cli/server_set_rdns.go index baa36a95..220501ba 100644 --- a/cli/server_set_rdns.go +++ b/cli/server_set_rdns.go @@ -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" ) @@ -12,6 +13,7 @@ func newServerSetRDNSCommand(cli *CLI) *cobra.Command { Use: "set-rdns [FLAGS] SERVER", Short: "Change reverse DNS of a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_shutdown.go b/cli/server_shutdown.go index a0c6a85e..bf4c03b4 100644 --- a/cli/server_shutdown.go +++ b/cli/server_shutdown.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newServerShutdownCommand(cli *CLI) *cobra.Command { Use: "shutdown [FLAGS] SERVER", Short: "Shutdown a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_ssh.go b/cli/server_ssh.go index 2f4e3b4e..07ef62e6 100644 --- a/cli/server_ssh.go +++ b/cli/server_ssh.go @@ -7,6 +7,7 @@ import ( "strconv" "syscall" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -15,6 +16,7 @@ func newServerSSHCommand(cli *CLI) *cobra.Command { Use: "ssh [FLAGS] SERVER [COMMAND...]", Short: "Spawn an SSH connection for the server", Args: cobra.MinimumNArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/server_update.go b/cli/server_update.go index 9f48f874..5ff3eadf 100644 --- a/cli/server_update.go +++ b/cli/server_update.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newServerUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] SERVER", Short: "Update a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/servertypes.go b/cli/servertypes.go index f573d603..f8396b5a 100644 --- a/cli/servertypes.go +++ b/cli/servertypes.go @@ -9,7 +9,6 @@ func newServerTypeCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runServer), } cmd.AddCommand( newServerTypeListCommand(cli), @@ -17,7 +16,3 @@ func newServerTypeCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runServerType(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/servertypes_describe.go b/cli/servertypes_describe.go index d2305790..7d494a20 100644 --- a/cli/servertypes_describe.go +++ b/cli/servertypes_describe.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newServerTypeDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] SERVERTYPE", Short: "Describe a server type", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.ServerTypeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/sshkey.go b/cli/sshkey.go index d6572507..93e6a496 100644 --- a/cli/sshkey.go +++ b/cli/sshkey.go @@ -9,7 +9,6 @@ func newSSHKeyCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runSSHKey), } cmd.AddCommand( newSSHKeyListCommand(cli), @@ -22,7 +21,3 @@ func newSSHKeyCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runSSHKey(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/sshkey_add_label.go b/cli/sshkey_add_label.go index e58515ac..279c007c 100644 --- a/cli/sshkey_add_label.go +++ b/cli/sshkey_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newSSHKeyAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] SSHKEY LABEL", Short: "Add a label to a SSH key", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.SSHKeyNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateSSHKeyAddLabel, cli.ensureToken), diff --git a/cli/sshkey_delete.go b/cli/sshkey_delete.go index 75d71835..3dfac6d7 100644 --- a/cli/sshkey_delete.go +++ b/cli/sshkey_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newSSHKeyDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] SSHKEY", Short: "Delete a SSH key", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.SSHKeyNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/sshkey_describe.go b/cli/sshkey_describe.go index c96db44d..d592e7a6 100644 --- a/cli/sshkey_describe.go +++ b/cli/sshkey_describe.go @@ -7,6 +7,7 @@ import ( "github.com/dustin/go-humanize" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -16,6 +17,7 @@ func newSSHKeyDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] SSHKEY", Short: "Describe a SSH key", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.SSHKeyNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/sshkey_remove_label.go b/cli/sshkey_remove_label.go index 35591ee1..737fe352 100644 --- a/cli/sshkey_remove_label.go +++ b/cli/sshkey_remove_label.go @@ -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 newSSHKeyRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] SSHKEY LABELKEY", - Short: "Remove a label from a SSH key", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] SSHKEY LABELKEY", + Short: "Remove a label from a SSH key", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.SSHKeyNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.SSHKeyLabelKeys(args[0]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateSSHKeyRemoveLabel, cli.ensureToken), diff --git a/cli/sshkey_update.go b/cli/sshkey_update.go index b63d6c80..dba781d6 100644 --- a/cli/sshkey_update.go +++ b/cli/sshkey_update.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newSSHKeyUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] SSHKEY", Short: "Update a SSH key", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.SSHKeyNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume.go b/cli/volume.go index 5440842b..0da21744 100644 --- a/cli/volume.go +++ b/cli/volume.go @@ -9,7 +9,6 @@ func newVolumeCommand(cli *CLI) *cobra.Command { Args: cobra.NoArgs, TraverseChildren: true, DisableFlagsInUseLine: true, - RunE: cli.wrap(runVolume), } cmd.AddCommand( newVolumeListCommand(cli), @@ -27,7 +26,3 @@ func newVolumeCommand(cli *CLI) *cobra.Command { ) return cmd } - -func runVolume(cli *CLI, cmd *cobra.Command, args []string) error { - return cmd.Usage() -} diff --git a/cli/volume_add_label.go b/cli/volume_add_label.go index b52f5ffb..6566811e 100644 --- a/cli/volume_add_label.go +++ b/cli/volume_add_label.go @@ -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" ) @@ -12,6 +13,7 @@ func newVolumeAddLabelCommand(cli *CLI) *cobra.Command { Use: "add-label [FLAGS] VOLUME LABEL", Short: "Add a label to a volume", Args: cobra.ExactArgs(2), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateVolumeAddLabel, cli.ensureToken), diff --git a/cli/volume_attach.go b/cli/volume_attach.go index 383f21fe..ce4d73f3 100644 --- a/cli/volume_attach.go +++ b/cli/volume_attach.go @@ -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" ) @@ -12,15 +13,14 @@ func newVolumeAttachCommand(cli *CLI) *cobra.Command { Use: "attach [FLAGS] VOLUME", Short: "Attach a volume to a server", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, RunE: cli.wrap(runVolumeAttach), } cmd.Flags().String("server", "", "Server (ID or name) (required)") - cmd.Flag("server").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_server_names"}, - } + cmd.RegisterFlagCompletionFunc("server", cmpl.SuggestCandidatesF(cli.ServerNames)) cmd.MarkFlagRequired("server") cmd.Flags().Bool("automount", false, "Automount volume after attach") diff --git a/cli/volume_create.go b/cli/volume_create.go index 388808de..e0189822 100644 --- a/cli/volume_create.go +++ b/cli/volume_create.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -22,14 +23,10 @@ func newVolumeCreateCommand(cli *CLI) *cobra.Command { cmd.MarkFlagRequired("name") cmd.Flags().String("server", "", "Server (ID or name)") - cmd.Flag("server").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_server_names"}, - } + cmd.RegisterFlagCompletionFunc("server", cmpl.SuggestCandidatesF(cli.ServerNames)) cmd.Flags().String("location", "", "Location (ID or name)") - cmd.Flag("location").Annotations = map[string][]string{ - cobra.BashCompCustom: {"__hcloud_location_names"}, - } + cmd.RegisterFlagCompletionFunc("location", cmpl.SuggestCandidatesF(cli.LocationNames)) cmd.Flags().Int("size", 0, "Size (GB) (required)") cmd.MarkFlagRequired("size") diff --git a/cli/volume_delete.go b/cli/volume_delete.go index 57de33e8..ebe6bd93 100644 --- a/cli/volume_delete.go +++ b/cli/volume_delete.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newVolumeDeleteCommand(cli *CLI) *cobra.Command { Use: "delete [FLAGS] VOLUME", Short: "Delete a volume", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_describe.go b/cli/volume_describe.go index 75c24972..0990dbb5 100644 --- a/cli/volume_describe.go +++ b/cli/volume_describe.go @@ -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" ) @@ -14,6 +15,7 @@ func newVolumeDescribeCommand(cli *CLI) *cobra.Command { Use: "describe [FLAGS] VOLUME", Short: "Describe a volume", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_detach.go b/cli/volume_detach.go index f6e2e76a..abd2d5c0 100644 --- a/cli/volume_detach.go +++ b/cli/volume_detach.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newVolumeDetachCommand(cli *CLI) *cobra.Command { Use: "detach [FLAGS] VOLUME", Short: "Detach a volume", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_disable_protection.go b/cli/volume_disable_protection.go index b0a27a14..e07e4ebc 100644 --- a/cli/volume_disable_protection.go +++ b/cli/volume_disable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeDisableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "disable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Disable resource protection for a volume", - Args: cobra.MinimumNArgs(2), + Use: "disable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Disable resource protection for a volume", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.VolumeNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_enable_protection.go b/cli/volume_enable_protection.go index 3fb518d3..12f1cfc9 100644 --- a/cli/volume_enable_protection.go +++ b/cli/volume_enable_protection.go @@ -4,15 +4,20 @@ import ( "fmt" "strings" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) func newVolumeEnableProtectionCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "enable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", - Short: "Enable resource protection for a volume", - Args: cobra.MinimumNArgs(2), + Use: "enable-protection [FLAGS] VOLUME PROTECTIONLEVEL [PROTECTIONLEVEL...]", + Short: "Enable resource protection for a volume", + Args: cobra.MinimumNArgs(2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.VolumeNames), + cmpl.SuggestCandidates("delete"), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_list.go b/cli/volume_list.go index 5a6e42e6..93b3e21a 100644 --- a/cli/volume_list.go +++ b/cli/volume_list.go @@ -97,7 +97,7 @@ func describeVolumeListTableOutput(cli *CLI) *tableOutput { volume := obj.(*hcloud.Volume) var server string if volume.Server != nil && cli != nil { - return cli.GetServerName(volume.Server.ID) + return cli.ServerName(volume.Server.ID) } return na(server) })). diff --git a/cli/volume_remove_label.go b/cli/volume_remove_label.go index 4292895b..f7db9b83 100644 --- a/cli/volume_remove_label.go +++ b/cli/volume_remove_label.go @@ -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 newVolumeRemoveLabelCommand(cli *CLI) *cobra.Command { cmd := &cobra.Command{ - Use: "remove-label [FLAGS] VOLUME LABELKEY", - Short: "Remove a label from a volume", - Args: cobra.RangeArgs(1, 2), + Use: "remove-label [FLAGS] VOLUME LABELKEY", + Short: "Remove a label from a volume", + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: cmpl.SuggestArgs( + cmpl.SuggestCandidatesF(cli.VolumeNames), + cmpl.SuggestCandidatesCtx(func(_ *cobra.Command, args []string) []string { + if len(args) != 1 { + return nil + } + return cli.VolumeLabelKeys(args[1]) + }), + ), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: chainRunE(validateVolumeRemoveLabel, cli.ensureToken), diff --git a/cli/volume_resize.go b/cli/volume_resize.go index f7879146..53d8d370 100644 --- a/cli/volume_resize.go +++ b/cli/volume_resize.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/spf13/cobra" ) @@ -11,6 +12,7 @@ func newVolumeResizeCommand(cli *CLI) *cobra.Command { Use: "resize [FLAGS] VOLUME", Short: "Resize a volume", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/cli/volume_update.go b/cli/volume_update.go index f4fb160e..b787fb80 100644 --- a/cli/volume_update.go +++ b/cli/volume_update.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/hetznercloud/cli/internal/cmd/cmpl" "github.com/hetznercloud/hcloud-go/hcloud" "github.com/spf13/cobra" ) @@ -13,6 +14,7 @@ func newVolumeUpdateCommand(cli *CLI) *cobra.Command { Use: "update [FLAGS] VOLUME", Short: "Update a volume", Args: cobra.ExactArgs(1), + ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(cli.VolumeNames)), TraverseChildren: true, DisableFlagsInUseLine: true, PreRunE: cli.ensureToken, diff --git a/go.mod b/go.mod index d5b9ec21..2099f6dc 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,15 @@ module github.com/hetznercloud/cli require ( - github.com/cheggaaa/pb/v3 v3.0.4 + github.com/cheggaaa/pb/v3 v3.0.5 github.com/dustin/go-humanize v1.0.0 github.com/fatih/structs v1.1.0 github.com/hetznercloud/hcloud-go v1.20.0 - github.com/pelletier/go-toml v1.7.0 - github.com/spf13/cobra v0.0.7 + github.com/pelletier/go-toml v1.8.0 + github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 - golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 + github.com/stretchr/testify v1.6.1 + golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de ) -go 1.14 +go 1.15 diff --git a/go.sum b/go.sum index 6cf09181..14226c68 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= -github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -19,6 +19,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -70,8 +71,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -80,9 +81,10 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -101,16 +103,20 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= -github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -121,8 +127,8 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= -golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -143,9 +149,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -167,6 +172,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/cmd/cmpl/suggestions.go b/internal/cmd/cmpl/suggestions.go new file mode 100644 index 00000000..a52245f6 --- /dev/null +++ b/internal/cmd/cmpl/suggestions.go @@ -0,0 +1,95 @@ +package cmpl + +import ( + "strings" + + "github.com/spf13/cobra" +) + +// SuggestCandidates returns a function that selects all items from the list of +// candidates cs which have the prefix toComplete. If toComplete is empty cs is +// returned. +// +// The returned function is mainly intended to be passed to +// cobra/Command.RegisterFlagCompletionFunc or assigned to +// cobra/Command.ValidArgsFunction. +func SuggestCandidates(cs ...string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return SuggestCandidatesF(func() []string { + return cs + }) +} + +// SuggestCandidatesF returns a function that calls the candidate function cf +// to obtain a list of completion candidates. Once the list of candidates is +// obtained the function returned by SuggestCandidatesF behaves like the +// function returned by SuggestCandidates. +func SuggestCandidatesF( + cf func() []string, +) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return SuggestCandidatesCtx(func(*cobra.Command, []string) []string { + return cf() + }) +} + +// SuggestCandidatesCtx returns a function that uses the candidate function cf +// to obtain a list of completion candidates in the context of previously +// selected arguments and flags. This is mainly useful for completion candidates that +// depend on a previously selected item like a server. +// +// Once the list of candidates is obtained the function returned by +// SuggestCandidatesF behaves like the function returned by SuggestCandidates. +func SuggestCandidatesCtx( + cf func(*cobra.Command, []string) []string, +) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cs := cf(cmd, args) + if toComplete == "" { + return cs, cobra.ShellCompDirectiveDefault + } + + var sel []string + for _, c := range cs { + if !strings.HasPrefix(c, toComplete) { + continue + } + sel = append(sel, c) + } + + return sel, cobra.ShellCompDirectiveDefault + } + +} + +// SuggestNothing returns a function that provides no suggestions. +func SuggestNothing() func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveDefault + } +} + +// SuggestArgs returns a function that uses the slice of valid argument +// functions vfs to provide completion suggestions for the passed command line +// arguments. +// +// The selection of the respective entry in vfs is positional, i.e. to +// determine the suggestions for the fourth command line argument SuggestArgs +// calls the function at vfs[4] if it exists. To skip suggestions for an +// argument in the middle of a list of arguments pass either nil or +// SuggestNothing. Using SuggestNothing is preferred. +func SuggestArgs( + vfs ...func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective), +) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + // Number of argument to suggest. args contains the already present + // command line arguments. + argNo := len(args) + + // Skip completion if not enough vfs have been passed, or if vfs at + // argNo is nil. + if len(vfs) <= argNo || vfs[argNo] == nil { + return nil, cobra.ShellCompDirectiveDefault + } + f := vfs[argNo] + return f(cmd, args, toComplete) + } +} diff --git a/internal/cmd/cmpl/suggestions_test.go b/internal/cmd/cmpl/suggestions_test.go new file mode 100644 index 00000000..d2992d5f --- /dev/null +++ b/internal/cmd/cmpl/suggestions_test.go @@ -0,0 +1,104 @@ +package cmpl_test + +import ( + "testing" + + "github.com/hetznercloud/cli/internal/cmd/cmpl" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestSuggestCandidates(t *testing.T) { + tests := []struct { + name string + cs []string + toComplete string + sug []string + d cobra.ShellCompDirective + }{ + { + name: "no prefix available", + cs: []string{"yaml", "json", "toml"}, + sug: []string{"yaml", "json", "toml"}, + }, + { + name: "prefix available", + cs: []string{"a", "aa", "aaa", "bbb"}, + toComplete: "aa", + sug: []string{"aa", "aaa"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + f := cmpl.SuggestCandidates(tt.cs...) + sug, d := f(nil, nil, tt.toComplete) + assert.Equal(t, tt.sug, sug) + assert.Equal(t, tt.d, d) + }) + } +} + +func TestSuggestArgs(t *testing.T) { + tests := []struct { + name string + vfs []func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) + args []string + sug []string + d cobra.ShellCompDirective + }{ + { + name: "suggest first argument but no vfs provided", + }, + { + name: "suggest second argument but no vfs provided", + args: []string{"aaaa"}, + }, + { + name: "suggest the only argument", + vfs: []func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective){ + cmpl.SuggestCandidates("aaaa"), + }, + sug: []string{"aaaa"}, + }, + { + name: "suggest the second of three possible arguments", + vfs: []func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective){ + cmpl.SuggestCandidates("aaaa"), + cmpl.SuggestCandidates("bbbb"), + cmpl.SuggestCandidates("cccc"), + }, + args: []string{"aaaa"}, + sug: []string{"bbbb"}, + }, + { + name: "skip suggestions using SuggestNothing", + vfs: []func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective){ + cmpl.SuggestCandidates("aaaa"), + cmpl.SuggestNothing(), + cmpl.SuggestCandidates("cccc"), + }, + args: []string{"aaaa"}, + }, + { + name: "skip suggestions using nil", + vfs: []func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective){ + cmpl.SuggestCandidates("aaaa"), + nil, + cmpl.SuggestCandidates("cccc"), + }, + args: []string{"aaaa"}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + f := cmpl.SuggestArgs(tt.vfs...) + sug, d := f(nil, tt.args, "") + assert.Equal(t, tt.sug, sug) + assert.Equal(t, tt.d, d) + }) + } +} diff --git a/internal/hcapi/certificate.go b/internal/hcapi/certificate.go new file mode 100644 index 00000000..3c33bb32 --- /dev/null +++ b/internal/hcapi/certificate.go @@ -0,0 +1,42 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// CertificateClient embeds the Hetzner Cloud Certificate client and provides +// some additional helper functions. +type CertificateClient struct { + *hcloud.CertificateClient +} + +// CertificateNames obtains a list of available certificates. It returns nil if +// the certificate names could not be fetched or there were no certificates. +func (c *CertificateClient) CertificateNames() []string { + certs, err := c.All(context.Background()) + if err != nil || len(certs) == 0 { + return nil + } + names := make([]string, len(certs)) + for i, cert := range certs { + name := cert.Name + if name == "" { + name = strconv.Itoa(cert.ID) + } + names[i] = name + } + return names +} + +// CertificateLabelKeys returns a slice containing the keys of all labels +// assigned to the certificate with the passed idOrName. +func (c *CertificateClient) CertificateLabelKeys(idOrName string) []string { + cert, _, err := c.Get(context.Background(), idOrName) + if err != nil || cert == nil || len(cert.Labels) == 0 { + return nil + } + return lkeys(cert.Labels) +} diff --git a/internal/hcapi/datacenter.go b/internal/hcapi/datacenter.go new file mode 100644 index 00000000..fe1a59af --- /dev/null +++ b/internal/hcapi/datacenter.go @@ -0,0 +1,32 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// DataCenterClient embeds the Hetzner Cloud DataCenter client and provides some +// additional helper functions. +type DataCenterClient struct { + *hcloud.DatacenterClient +} + +// DataCenterNames obtains a list of available data centers. It returns nil if +// data center names could not be fetched. +func (c *DataCenterClient) DataCenterNames() []string { + dcs, err := c.All(context.Background()) + if err != nil || len(dcs) == 0 { + return nil + } + names := make([]string, len(dcs)) + for i, dc := range dcs { + name := dc.Name + if name == "" { + name = strconv.Itoa(dc.ID) + } + names[i] = name + } + return names +} diff --git a/internal/hcapi/floatingip.go b/internal/hcapi/floatingip.go new file mode 100644 index 00000000..8ca2c83f --- /dev/null +++ b/internal/hcapi/floatingip.go @@ -0,0 +1,42 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// FloatingIPClient embeds the Hetzner Cloud FloatingIP client and provides some +// additional helper functions. +type FloatingIPClient struct { + *hcloud.FloatingIPClient +} + +// FloatingIPNames obtains a list of available floating IPs. It returns nil if +// no floating IP names could be fetched or none were available. +func (c *FloatingIPClient) FloatingIPNames() []string { + fips, err := c.All(context.Background()) + if err != nil || len(fips) == 0 { + return nil + } + names := make([]string, len(fips)) + for i, fip := range fips { + name := fip.Name + if name == "" { + name = strconv.Itoa(fip.ID) + } + names[i] = name + } + return names +} + +// FloatingIPLabelKeys returns a slice containing the keys of all labels +// assigned to the Floating IP with the passed idOrName. +func (c *FloatingIPClient) FloatingIPLabelKeys(idOrName string) []string { + fip, _, err := c.Get(context.Background(), idOrName) + if err != nil || fip == nil || len(fip.Labels) == 0 { + return nil + } + return lkeys(fip.Labels) +} diff --git a/internal/hcapi/image.go b/internal/hcapi/image.go new file mode 100644 index 00000000..a0ea931c --- /dev/null +++ b/internal/hcapi/image.go @@ -0,0 +1,42 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// ImageClient embeds the Hetzner Cloud Image client and provides some +// additional helper functions. +type ImageClient struct { + *hcloud.ImageClient +} + +// ImageNames obtains a list of available images. It returns nil if image names +// could not be fetched. +func (c *ImageClient) ImageNames() []string { + imgs, err := c.All(context.Background()) + if err != nil || len(imgs) == 0 { + return nil + } + names := make([]string, len(imgs)) + for i, img := range imgs { + name := img.Name + if name == "" { + name = strconv.Itoa(img.ID) + } + names[i] = name + } + return names +} + +// ImageLabelKeys returns a slice containing the keys of all labels assigned to +// the Image with the passed idOrName. +func (c *ImageClient) ImageLabelKeys(idOrName string) []string { + img, _, err := c.Get(context.Background(), idOrName) + if err != nil || img == nil || len(img.Labels) == 0 { + return nil + } + return lkeys(img.Labels) +} diff --git a/internal/hcapi/iso.go b/internal/hcapi/iso.go new file mode 100644 index 00000000..62919b1f --- /dev/null +++ b/internal/hcapi/iso.go @@ -0,0 +1,33 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// ISOClient embeds the Hetzner Cloud ISO client and provides some additional +// helper functions. +type ISOClient struct { + *hcloud.ISOClient +} + +// ISONames obtains a list of available ISOs for the current account. It +// returns nil if the current project has no ISOs or the ISO names could not be +// fetched. +func (c *ISOClient) ISONames() []string { + isos, err := c.All(context.Background()) + if err != nil || len(isos) == 0 { + return nil + } + names := make([]string, len(isos)) + for i, iso := range isos { + name := iso.Name + if name == "" { + name = strconv.Itoa(iso.ID) + } + names[i] = name + } + return names +} diff --git a/internal/hcapi/loadbalancer.go b/internal/hcapi/loadbalancer.go new file mode 100644 index 00000000..19e40eda --- /dev/null +++ b/internal/hcapi/loadbalancer.go @@ -0,0 +1,60 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// LoadBalancerClient embeds the Hetzner Cloud Load Balancer client and +// provides some additional helper functions. As a convenience it references +// the type client as well and provides a unified interface to Load Balancers +// and their types. +type LoadBalancerClient struct { + *hcloud.LoadBalancerClient + + TypeClient *hcloud.LoadBalancerTypeClient +} + +// LoadBalancerNames obtains a list of all available Load Balancer names. It +// returns nil if the names could not be fetched. +func (c *LoadBalancerClient) LoadBalancerNames() []string { + lbs, err := c.All(context.Background()) + if err != nil || len(lbs) == 0 { + return nil + } + names := make([]string, len(lbs)) + for i, lb := range lbs { + name := lb.Name + if name == "" { + name = strconv.Itoa(lb.ID) + } + names[i] = name + } + return names +} + +// LoadBalancerLabelKeys returns a slice containing the keys of all labels +// assigned to the Load Balancer with the passed idOrName. +func (c *LoadBalancerClient) LoadBalancerLabelKeys(idOrName string) []string { + lb, _, err := c.Get(context.Background(), idOrName) + if err != nil || lb == nil || len(lb.Labels) == 0 { + return nil + } + return lkeys(lb.Labels) +} + +// LoadBalancerTypeNames returns a slice containing the names of all available +// Load Balancer types. +func (c *LoadBalancerClient) LoadBalancerTypeNames() []string { + types, err := c.TypeClient.All(context.Background()) + if err != nil || len(types) == 0 { + return nil + } + names := make([]string, len(types)) + for i, typ := range types { + names[i] = typ.Name + } + return names +} diff --git a/internal/hcapi/location.go b/internal/hcapi/location.go new file mode 100644 index 00000000..f76aa973 --- /dev/null +++ b/internal/hcapi/location.go @@ -0,0 +1,32 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// LocationClient embeds the Hetzner Cloud Location client and provides some +// additional helper functions. +type LocationClient struct { + *hcloud.LocationClient +} + +// LocationNames obtains a list of available locations. It returns nil if +// location names could not be fetched. +func (c *LocationClient) LocationNames() []string { + locs, err := c.All(context.Background()) + if err != nil || len(locs) == 0 { + return nil + } + names := make([]string, len(locs)) + for i, loc := range locs { + name := loc.Name + if name == "" { + name = strconv.Itoa(loc.ID) + } + names[i] = name + } + return names +} diff --git a/internal/hcapi/network.go b/internal/hcapi/network.go new file mode 100644 index 00000000..af1cb7d1 --- /dev/null +++ b/internal/hcapi/network.go @@ -0,0 +1,93 @@ +package hcapi + +import ( + "context" + "strconv" + "sync" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// NetworkClient embeds the Hetzner Cloud Network client and provides some +// additional helper functions. +type NetworkClient struct { + *hcloud.NetworkClient + + netsByID map[int]*hcloud.Network + netsByName map[string]*hcloud.Network + + once sync.Once + err error +} + +// NetworkName obtains the name of the network with id. If the name could not +// be fetched it returns the value id converted to a string. +func (c *NetworkClient) NetworkName(id int) string { + if err := c.init(); err != nil { + return strconv.Itoa(id) + } + + net, ok := c.netsByID[id] + if !ok || net.Name == "" { + return strconv.Itoa(id) + } + return net.Name +} + +// NetworkNames obtains a list of available networks. It returns nil if the +// network names could not be fetched or if there are no networks. +func (c *NetworkClient) NetworkNames() []string { + if err := c.init(); err != nil || len(c.netsByID) == 0 { + return nil + } + names := make([]string, len(c.netsByID)) + i := 0 + for _, net := range c.netsByID { + name := net.Name + if name == "" { + name = strconv.Itoa(net.ID) + } + names[i] = name + i++ + } + return names +} + +// NetworkLabelKeys returns a slice containing the keys of all labels assigned +// to the Network with the passed idOrName. +func (c *NetworkClient) NetworkLabelKeys(idOrName string) []string { + var net *hcloud.Network + + if err := c.init(); err != nil || len(c.netsByID) == 0 { + return nil + } + if id, err := strconv.Atoi(idOrName); err != nil { + net = c.netsByID[id] + } + if v, ok := c.netsByName[idOrName]; ok && net == nil { + net = v + } + if net == nil || len(net.Labels) == 0 { + return nil + } + return lkeys(net.Labels) +} + +func (c *NetworkClient) init() error { + c.once.Do(func() { + nets, err := c.All(context.Background()) + if err != nil { + c.err = err + } + if c.err != nil || len(nets) == 0 { + return + } + c.netsByID = make(map[int]*hcloud.Network, len(nets)) + c.netsByName = make(map[string]*hcloud.Network, len(nets)) + for _, net := range nets { + c.netsByID[net.ID] = net + c.netsByName[net.Name] = net + } + }) + return c.err +} diff --git a/internal/hcapi/server.go b/internal/hcapi/server.go new file mode 100644 index 00000000..32111de4 --- /dev/null +++ b/internal/hcapi/server.go @@ -0,0 +1,112 @@ +package hcapi + +import ( + "context" + "strconv" + "sync" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// ServerClient embeds the Hetzner Cloud Server client and provides some +// additional helper functions. +type ServerClient struct { + *hcloud.ServerClient + + ServerTypes *hcloud.ServerTypeClient + + srvByID map[int]*hcloud.Server + srvByName map[string]*hcloud.Server + + once sync.Once + err error +} + +// ServerName obtains the name of the server with id. If the name could not +// be fetched it returns the value id converted to a string. +func (c *ServerClient) ServerName(id int) string { + if err := c.init(); err != nil { + return strconv.Itoa(id) + } + + srv, ok := c.srvByID[id] + if !ok || srv.Name == "" { + return strconv.Itoa(id) + } + return srv.Name +} + +// ServerNames obtains a list of available servers. It returns nil if the +// server names could not be fetched or if there are no servers. +func (c *ServerClient) ServerNames() []string { + if err := c.init(); err != nil || len(c.srvByID) == 0 { + return nil + } + names := make([]string, len(c.srvByID)) + i := 0 + for _, srv := range c.srvByID { + name := srv.Name + if name == "" { + name = strconv.Itoa(srv.ID) + } + names[i] = name + i++ + } + return names +} + +// ServerLabelKeys returns a slice containing the keys of all labels assigned +// to the Server with the passed idOrName. +func (c *ServerClient) ServerLabelKeys(idOrName string) []string { + var srv *hcloud.Server + + if err := c.init(); err != nil || len(c.srvByID) == 0 { + return nil + } + // Try to get server by ID. + if id, err := strconv.Atoi(idOrName); err != nil { + srv = c.srvByID[id] + } + // If the above failed idOrName might contain a server name. If srv is not + // nil at this point and we found something in the map, someone gave their + // server a name containing the ID of another server. + if v, ok := c.srvByName[idOrName]; ok && srv == nil { + srv = v + } + if srv == nil || len(srv.Labels) == 0 { + return nil + } + return lkeys(srv.Labels) +} + +// ServerTypeNames returns a slice of all available server types. +func (c *ServerClient) ServerTypeNames() []string { + sts, err := c.ServerTypes.All(context.Background()) + if err == nil || len(sts) == 0 { + return nil + } + names := make([]string, len(sts)) + for i, st := range sts { + names[i] = st.Name + } + return names +} + +func (c *ServerClient) init() error { + c.once.Do(func() { + srvs, err := c.All(context.Background()) + if err != nil { + c.err = err + } + if c.err != nil || len(srvs) == 0 { + return + } + c.srvByID = make(map[int]*hcloud.Server, len(srvs)) + c.srvByName = make(map[string]*hcloud.Server, len(srvs)) + for _, srv := range srvs { + c.srvByID[srv.ID] = srv + c.srvByName[srv.Name] = srv + } + }) + return c.err +} diff --git a/internal/hcapi/sshkey.go b/internal/hcapi/sshkey.go new file mode 100644 index 00000000..a0960adc --- /dev/null +++ b/internal/hcapi/sshkey.go @@ -0,0 +1,42 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// SSHKeyClient embeds the Hetzner Cloud SSHKey client and provides some +// additional helper functions. +type SSHKeyClient struct { + *hcloud.SSHKeyClient +} + +// SSHKeyNames obtains a list of available SSH keys. It returns nil if SSH key +// names could not be fetched or none are available. +func (c *SSHKeyClient) SSHKeyNames() []string { + sshKeys, err := c.All(context.Background()) + if err != nil || len(sshKeys) == 0 { + return nil + } + names := make([]string, len(sshKeys)) + for i, key := range sshKeys { + name := key.Name + if name == "" { + name = strconv.Itoa(key.ID) + } + names[i] = name + } + return names +} + +// SSHKeyLabelKeys returns a slice containing the keys of all labels +// assigned to the SSH Key with the passed idOrName. +func (c *SSHKeyClient) SSHKeyLabelKeys(idOrName string) []string { + sshKey, _, err := c.Get(context.Background(), idOrName) + if err != nil || sshKey == nil || len(sshKey.Labels) == 0 { + return nil + } + return lkeys(sshKey.Labels) +} diff --git a/internal/hcapi/util.go b/internal/hcapi/util.go new file mode 100644 index 00000000..135c4972 --- /dev/null +++ b/internal/hcapi/util.go @@ -0,0 +1,11 @@ +package hcapi + +func lkeys(m map[string]string) []string { + ks := make([]string, len(m)) + i := 0 + for k := range m { + ks[i] = k + i++ + } + return ks +} diff --git a/internal/hcapi/volume.go b/internal/hcapi/volume.go new file mode 100644 index 00000000..02a9ab21 --- /dev/null +++ b/internal/hcapi/volume.go @@ -0,0 +1,43 @@ +package hcapi + +import ( + "context" + "strconv" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// VolumeClient embeds the Hetzner Cloud Volume client and provides some additional +// helper functions. +type VolumeClient struct { + *hcloud.VolumeClient +} + +// VolumeNames obtains a list of available volumes for the current account. It +// returns nil if the current project has no volumes or the volume names could +// not be fetched. +func (c *VolumeClient) VolumeNames() []string { + vols, err := c.All(context.Background()) + if err != nil || len(vols) == 0 { + return nil + } + names := make([]string, len(vols)) + for i, vol := range vols { + name := vol.Name + if name == "" { + name = strconv.Itoa(vol.ID) + } + names[i] = name + } + return names +} + +// VolumeLabelKeys returns a slice containing the keys of all labels assigned +// to the Volume with the passed idOrName. +func (c *VolumeClient) VolumeLabelKeys(idOrName string) []string { + vol, _, err := c.Get(context.Background(), idOrName) + if err != nil || vol == nil || len(vol.Labels) == 0 { + return nil + } + return lkeys(vol.Labels) +}