Skip to content

Commit

Permalink
fix: use --poll-interval flag (#660)
Browse files Browse the repository at this point in the history
The `--poll-interval` flag was ignored since a refactoring in #293
(released first in v1.21.0). This adds back the functionality that was
lost then.
phm07 authored Jan 9, 2024
1 parent 2df5e92 commit b9328a6
Showing 8 changed files with 265 additions and 71 deletions.
9 changes: 9 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@ import (
"github.com/hetznercloud/cli/internal/cmd/version"
"github.com/hetznercloud/cli/internal/cmd/volume"
"github.com/hetznercloud/cli/internal/state"
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

func NewRootCommand(s state.State) *cobra.Command {
@@ -63,5 +64,13 @@ func NewRootCommand(s state.State) *cobra.Command {
)
cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress")
cmd.SetOut(os.Stdout)
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
pollInterval, err := cmd.Flags().GetDuration("poll-interval")
if err != nil {
return err
}
s.Client().WithOpts(hcloud.WithPollBackoffFunc(hcloud.ConstantBackoff(pollInterval)))
return nil
}
return cmd
}
20 changes: 20 additions & 0 deletions internal/hcapi2/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hcapi2

import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

// ActionClient embeds the Hetzner Cloud Action client
type ActionClient interface {
hcloud.IActionClient
}

func NewActionClient(client hcloud.IActionClient) ActionClient {
return &actionClient{
IActionClient: client,
}
}

type actionClient struct {
hcloud.IActionClient
}
146 changes: 89 additions & 57 deletions internal/hcapi2/client.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import (

// Client makes all API clients accessible via a single interface.
type Client interface {
Action() ActionClient
Datacenter() DatacenterClient
Firewall() FirewallClient
FloatingIP() FloatingIPClient
@@ -25,10 +26,11 @@ type Client interface {
PlacementGroup() PlacementGroupClient
RDNS() RDNSClient
PrimaryIP() PrimaryIPClient
WithOpts(...hcloud.ClientOption)
}

type client struct {
client *hcloud.Client
type clientCache struct {
actionClient ActionClient
certificateClient CertificateClient
datacenterClient DatacenterClient
serverClient ServerClient
@@ -46,162 +48,192 @@ type client struct {
placementGroupClient PlacementGroupClient
rdnsClient RDNSClient
primaryIPClient PrimaryIPClient
}

type client struct {
client *hcloud.Client
cache clientCache

mu sync.Mutex
mu sync.Mutex
opts []hcloud.ClientOption
}

// NewClient creates a new CLI API client extending hcloud.Client.
func NewClient(c *hcloud.Client) Client {
return &client{
client: c,
func NewClient(opts ...hcloud.ClientOption) Client {
c := &client{
opts: opts,
}
c.update()
return c
}

func (c *client) WithOpts(opts ...hcloud.ClientOption) {
c.mu.Lock()
defer c.mu.Unlock()
c.opts = append(c.opts, opts...)
c.update()
}

func (c *client) update() {
c.client = hcloud.NewClient(c.opts...)
c.cache = clientCache{}
}

func (c *client) Action() ActionClient {
c.mu.Lock()
if c.cache.actionClient == nil {
c.cache.actionClient = NewActionClient(&c.client.Action)
}
defer c.mu.Unlock()
return c.cache.actionClient
}

func (c *client) Certificate() CertificateClient {
c.mu.Lock()
if c.certificateClient == nil {
c.certificateClient = NewCertificateClient(&c.client.Certificate)
if c.cache.certificateClient == nil {
c.cache.certificateClient = NewCertificateClient(&c.client.Certificate)
}
defer c.mu.Unlock()
return c.certificateClient
return c.cache.certificateClient
}

func (c *client) Datacenter() DatacenterClient {
c.mu.Lock()
if c.datacenterClient == nil {
c.datacenterClient = NewDatacenterClient(&c.client.Datacenter)
if c.cache.datacenterClient == nil {
c.cache.datacenterClient = NewDatacenterClient(&c.client.Datacenter)
}
defer c.mu.Unlock()
return c.datacenterClient
return c.cache.datacenterClient
}

func (c *client) Firewall() FirewallClient {
c.mu.Lock()
if c.firewallClient == nil {
c.firewallClient = NewFirewallClient(&c.client.Firewall)
if c.cache.firewallClient == nil {
c.cache.firewallClient = NewFirewallClient(&c.client.Firewall)
}
defer c.mu.Unlock()
return c.firewallClient
return c.cache.firewallClient
}

func (c *client) FloatingIP() FloatingIPClient {
c.mu.Lock()
if c.floatingIPClient == nil {
c.floatingIPClient = NewFloatingIPClient(&c.client.FloatingIP)
if c.cache.floatingIPClient == nil {
c.cache.floatingIPClient = NewFloatingIPClient(&c.client.FloatingIP)
}
defer c.mu.Unlock()
return c.floatingIPClient
return c.cache.floatingIPClient
}

func (c *client) PrimaryIP() PrimaryIPClient {
c.mu.Lock()
if c.primaryIPClient == nil {
c.primaryIPClient = NewPrimaryIPClient(&c.client.PrimaryIP)
if c.cache.primaryIPClient == nil {
c.cache.primaryIPClient = NewPrimaryIPClient(&c.client.PrimaryIP)
}
defer c.mu.Unlock()
return c.primaryIPClient
return c.cache.primaryIPClient
}

func (c *client) Image() ImageClient {
c.mu.Lock()
if c.imageClient == nil {
c.imageClient = NewImageClient(&c.client.Image)
if c.cache.imageClient == nil {
c.cache.imageClient = NewImageClient(&c.client.Image)
}
defer c.mu.Unlock()
return c.imageClient
return c.cache.imageClient
}

func (c *client) ISO() ISOClient {
c.mu.Lock()
if c.isoClient == nil {
c.isoClient = NewISOClient(&c.client.ISO)
if c.cache.isoClient == nil {
c.cache.isoClient = NewISOClient(&c.client.ISO)
}
defer c.mu.Unlock()
return c.isoClient
return c.cache.isoClient
}

func (c *client) Location() LocationClient {
c.mu.Lock()
if c.locationClient == nil {
c.locationClient = NewLocationClient(&c.client.Location)
if c.cache.locationClient == nil {
c.cache.locationClient = NewLocationClient(&c.client.Location)
}
defer c.mu.Unlock()
return c.locationClient
return c.cache.locationClient
}

func (c *client) LoadBalancer() LoadBalancerClient {
c.mu.Lock()
if c.loadBalancerClient == nil {
c.loadBalancerClient = NewLoadBalancerClient(&c.client.LoadBalancer)
if c.cache.loadBalancerClient == nil {
c.cache.loadBalancerClient = NewLoadBalancerClient(&c.client.LoadBalancer)
}
defer c.mu.Unlock()
return c.loadBalancerClient
return c.cache.loadBalancerClient
}
func (c *client) LoadBalancerType() LoadBalancerTypeClient {
c.mu.Lock()
if c.loadBalancerTypeClient == nil {
c.loadBalancerTypeClient = NewLoadBalancerTypeClient(&c.client.LoadBalancerType)
if c.cache.loadBalancerTypeClient == nil {
c.cache.loadBalancerTypeClient = NewLoadBalancerTypeClient(&c.client.LoadBalancerType)
}
defer c.mu.Unlock()
return c.loadBalancerTypeClient
return c.cache.loadBalancerTypeClient
}
func (c *client) Network() NetworkClient {
c.mu.Lock()
if c.networkClient == nil {
c.networkClient = NewNetworkClient(&c.client.Network)
if c.cache.networkClient == nil {
c.cache.networkClient = NewNetworkClient(&c.client.Network)
}
defer c.mu.Unlock()
return c.networkClient
return c.cache.networkClient
}

func (c *client) Server() ServerClient {
c.mu.Lock()
if c.serverClient == nil {
c.serverClient = NewServerClient(&c.client.Server)
if c.cache.serverClient == nil {
c.cache.serverClient = NewServerClient(&c.client.Server)
}
defer c.mu.Unlock()
return c.serverClient
return c.cache.serverClient
}

func (c *client) ServerType() ServerTypeClient {
c.mu.Lock()
if c.serverTypeClient == nil {
c.serverTypeClient = NewServerTypeClient(&c.client.ServerType)
if c.cache.serverTypeClient == nil {
c.cache.serverTypeClient = NewServerTypeClient(&c.client.ServerType)
}
defer c.mu.Unlock()
return c.serverTypeClient
return c.cache.serverTypeClient
}

func (c *client) SSHKey() SSHKeyClient {
c.mu.Lock()
if c.sshKeyClient == nil {
c.sshKeyClient = NewSSHKeyClient(&c.client.SSHKey)
if c.cache.sshKeyClient == nil {
c.cache.sshKeyClient = NewSSHKeyClient(&c.client.SSHKey)
}
defer c.mu.Unlock()
return c.sshKeyClient
return c.cache.sshKeyClient
}
func (c *client) RDNS() RDNSClient {
c.mu.Lock()
if c.rdnsClient == nil {
c.rdnsClient = NewRDNSClient(&c.client.RDNS)
if c.cache.rdnsClient == nil {
c.cache.rdnsClient = NewRDNSClient(&c.client.RDNS)
}
defer c.mu.Unlock()
return c.rdnsClient
return c.cache.rdnsClient
}

func (c *client) Volume() VolumeClient {
c.mu.Lock()
if c.volumeClient == nil {
c.volumeClient = NewVolumeClient(&c.client.Volume)
if c.cache.volumeClient == nil {
c.cache.volumeClient = NewVolumeClient(&c.client.Volume)
}
defer c.mu.Unlock()
return c.volumeClient
return c.cache.volumeClient
}

func (c *client) PlacementGroup() PlacementGroupClient {
c.mu.Lock()
if c.placementGroupClient == nil {
c.placementGroupClient = NewPlacementGroupClient(&c.client.PlacementGroup)
if c.cache.placementGroupClient == nil {
c.cache.placementGroupClient = NewPlacementGroupClient(&c.client.PlacementGroup)
}
defer c.mu.Unlock()
return c.placementGroupClient
return c.cache.placementGroupClient
}
13 changes: 13 additions & 0 deletions internal/hcapi2/mock/client.go
Original file line number Diff line number Diff line change
@@ -4,9 +4,11 @@ import (
"github.com/golang/mock/gomock"

"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

type MockClient struct {
ActionClient *MockActionClient
CertificateClient *MockCertificateClient
DatacenterClient *MockDatacenterClient
FirewallClient *MockFirewallClient
@@ -28,6 +30,7 @@ type MockClient struct {

func NewMockClient(ctrl *gomock.Controller) *MockClient {
return &MockClient{
ActionClient: NewMockActionClient(ctrl),
CertificateClient: NewMockCertificateClient(ctrl),
DatacenterClient: NewMockDatacenterClient(ctrl),
FirewallClient: NewMockFirewallClient(ctrl),
@@ -47,9 +50,15 @@ func NewMockClient(ctrl *gomock.Controller) *MockClient {
RDNSClient: NewMockRDNSClient(ctrl),
}
}

func (c *MockClient) Action() hcapi2.ActionClient {
return c.ActionClient
}

func (c *MockClient) Certificate() hcapi2.CertificateClient {
return c.CertificateClient
}

func (c *MockClient) Datacenter() hcapi2.DatacenterClient {
return c.DatacenterClient
}
@@ -112,3 +121,7 @@ func (c *MockClient) RDNS() hcapi2.RDNSClient {
func (c *MockClient) PlacementGroup() hcapi2.PlacementGroupClient {
return c.PlacementGroupClient
}

func (*MockClient) WithOpts(...hcloud.ClientOption) {
// no-op
}
1 change: 1 addition & 0 deletions internal/hcapi2/mock/mock_gen.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package hcapi2_mock

//go:generate mockgen -package hcapi2_mock -destination zz_action_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ActionClient
//go:generate mockgen -package hcapi2_mock -destination zz_certificate_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 CertificateClient
//go:generate mockgen -package hcapi2_mock -destination zz_datacenter_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 DatacenterClient
//go:generate mockgen -package hcapi2_mock -destination zz_image_client_mock.go github.com/hetznercloud/cli/internal/hcapi2 ImageClient
128 changes: 128 additions & 0 deletions internal/hcapi2/mock/zz_action_client_mock.go
4 changes: 2 additions & 2 deletions internal/state/helpers.go
Original file line number Diff line number Diff line change
@@ -34,7 +34,7 @@ func (c *state) ActionProgress(cmd *cobra.Command, ctx context.Context, action *
}

func (c *state) ActionsProgresses(cmd *cobra.Command, ctx context.Context, actions []*hcloud.Action) error {
progressCh, errCh := c.hcloudClient.Action.WatchOverallProgress(ctx, actions)
progressCh, errCh := c.Client().Action().WatchOverallProgress(ctx, actions)

if StdoutIsTerminal() {
progress := pb.New(100)
@@ -83,7 +83,7 @@ func (c *state) WaitForActions(cmd *cobra.Command, ctx context.Context, actions
waitingFor = fmt.Sprintf("Waiting for volume %d to have been attached to server %d", resources["volume"], resources["server"])
}

_, errCh := c.hcloudClient.Action.WatchProgress(ctx, action)
_, errCh := c.Client().Action().WatchProgress(ctx, action)

err := DisplayProgressCircle(cmd, errCh, waitingFor)
if err != nil {
15 changes: 3 additions & 12 deletions internal/state/state.go
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@ import (
"context"
"log"
"os"
"time"

"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/cli/internal/version"
@@ -29,7 +28,6 @@ type state struct {
debug bool
debugFilePath string
client hcapi2.Client
hcloudClient *hcloud.Client
config *Config
}

@@ -53,8 +51,7 @@ func New(cfg *Config) (State, error) {
}

s.readEnv()
s.hcloudClient = s.newClient()
s.client = hcapi2.NewClient(s.hcloudClient)
s.client = s.newClient()
return s, nil
}

@@ -112,7 +109,7 @@ func (c *state) readEnv() {
}
}

func (c *state) newClient() *hcloud.Client {
func (c *state) newClient() hcapi2.Client {
opts := []hcloud.ClientOption{
hcloud.WithToken(c.token),
hcloud.WithApplication("hcloud-cli", version.Version),
@@ -128,11 +125,5 @@ func (c *state) newClient() *hcloud.Client {
opts = append(opts, hcloud.WithDebugWriter(writer))
}
}
// TODO Somehow pass here
// pollInterval, _ := c.RootCommand.PersistentFlags().GetDuration("poll-interval")
pollInterval := 500 * time.Millisecond
if pollInterval > 0 {
opts = append(opts, hcloud.WithPollInterval(pollInterval))
}
return hcloud.NewClient(opts...)
return hcapi2.NewClient(opts...)
}

0 comments on commit b9328a6

Please sign in to comment.