Skip to content

Commit

Permalink
Merge pull request #6308 from hetznercloud/bump-hcloud-go
Browse files Browse the repository at this point in the history
chore(deps): update vendored hcloud-go to 2.4.0
  • Loading branch information
k8s-ci-robot authored Nov 28, 2023
2 parents e23e63a + a6f57ba commit 225fc33
Show file tree
Hide file tree
Showing 23 changed files with 231 additions and 126 deletions.
164 changes: 110 additions & 54 deletions cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,12 @@ func (a *Action) Error() error {

// ActionClient is a client for the actions API.
type ActionClient struct {
client *Client
action *ResourceActionClient
}

// GetByID retrieves an action by its ID. If the action does not exist, nil is returned.
func (c *ActionClient) GetByID(ctx context.Context, id int64) (*Action, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/actions/%d", id), nil)
if err != nil {
return nil, nil, err
}

var body schema.ActionGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ActionFromSchema(body.Action), resp, nil
return c.action.GetByID(ctx, id)
}

// ActionListOpts specifies options for listing actions.
Expand Down Expand Up @@ -120,47 +107,17 @@ func (l ActionListOpts) values() url.Values {
// Please note that filters specified in opts are not taken into account
// when their value corresponds to their zero value or when they are empty.
func (c *ActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) {
path := "/actions?" + opts.values().Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}

var body schema.ActionListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
actions := make([]*Action, 0, len(body.Actions))
for _, i := range body.Actions {
actions = append(actions, ActionFromSchema(i))
}
return actions, resp, nil
return c.action.List(ctx, opts)
}

// All returns all actions.
func (c *ActionClient) All(ctx context.Context) ([]*Action, error) {
return c.AllWithOpts(ctx, ActionListOpts{ListOpts: ListOpts{PerPage: 50}})
return c.action.All(ctx, ActionListOpts{ListOpts: ListOpts{PerPage: 50}})
}

// AllWithOpts returns all actions for the given options.
func (c *ActionClient) AllWithOpts(ctx context.Context, opts ActionListOpts) ([]*Action, error) {
var allActions []*Action

err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
actions, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allActions = append(allActions, actions...)
return resp, nil
})
if err != nil {
return nil, err
}

return allActions, nil
return c.action.All(ctx, opts)
}

// WatchOverallProgress watches several actions' progress until they complete
Expand Down Expand Up @@ -189,20 +146,21 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
defer close(errCh)
defer close(progressCh)

successIDs := make([]int64, 0, len(actions))
completedIDs := make([]int64, 0, len(actions))
watchIDs := make(map[int64]struct{}, len(actions))
for _, action := range actions {
watchIDs[action.ID] = struct{}{}
}

retries := 0
previousProgress := 0

for {
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-time.After(c.client.pollBackoffFunc(retries)):
case <-time.After(c.action.client.pollBackoffFunc(retries)):
retries++
}

Expand All @@ -216,21 +174,35 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
errCh <- err
return
}
if len(as) == 0 {
// No actions returned for the provided IDs, they do not exist in the API.
// We need to catch and fail early for this, otherwise the loop will continue
// indefinitely.
errCh <- fmt.Errorf("failed to wait for actions: remaining actions (%v) are not returned from API", opts.ID)
return
}

progress := 0
for _, a := range as {
switch a.Status {
case ActionStatusRunning:
continue
progress += a.Progress
case ActionStatusSuccess:
delete(watchIDs, a.ID)
successIDs = append(successIDs, a.ID)
sendProgress(progressCh, int(float64(len(actions)-len(successIDs))/float64(len(actions))*100))
completedIDs = append(completedIDs, a.ID)
case ActionStatusError:
delete(watchIDs, a.ID)
completedIDs = append(completedIDs, a.ID)
errCh <- fmt.Errorf("action %d failed: %w", a.ID, a.Error())
}
}

progress += len(completedIDs) * 100
if progress != 0 && progress != previousProgress {
sendProgress(progressCh, progress/len(actions))
previousProgress = progress
}

if len(watchIDs) == 0 {
return
}
Expand Down Expand Up @@ -273,7 +245,7 @@ func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-cha
case <-ctx.Done():
errCh <- ctx.Err()
return
case <-time.After(c.client.pollBackoffFunc(retries)):
case <-time.After(c.action.client.pollBackoffFunc(retries)):
retries++
}

Expand All @@ -282,6 +254,10 @@ func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-cha
errCh <- err
return
}
if a == nil {
errCh <- fmt.Errorf("failed to wait for action %d: action not returned from API", action.ID)
return
}

switch a.Status {
case ActionStatusRunning:
Expand All @@ -300,6 +276,7 @@ func (c *ActionClient) WatchProgress(ctx context.Context, action *Action) (<-cha
return progressCh, errCh
}

// sendProgress allows the user to only read from the error channel and ignore any progress updates.
func sendProgress(progressCh chan int, p int) {
select {
case progressCh <- p:
Expand All @@ -308,3 +285,82 @@ func sendProgress(progressCh chan int, p int) {
break
}
}

// ResourceActionClient is a client for the actions API exposed by the resource.
type ResourceActionClient struct {
resource string
client *Client
}

func (c *ResourceActionClient) getBaseURL() string {
if c.resource == "" {
return ""
}

return "/" + c.resource
}

// GetByID retrieves an action by its ID. If the action does not exist, nil is returned.
func (c *ResourceActionClient) GetByID(ctx context.Context, id int64) (*Action, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("%s/actions/%d", c.getBaseURL(), id), nil)
if err != nil {
return nil, nil, err
}

var body schema.ActionGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return ActionFromSchema(body.Action), resp, nil
}

// List returns a list of actions for a specific page.
//
// Please note that filters specified in opts are not taken into account
// when their value corresponds to their zero value or when they are empty.
func (c *ResourceActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) {
req, err := c.client.NewRequest(
ctx,
"GET",
fmt.Sprintf("%s/actions?%s", c.getBaseURL(), opts.values().Encode()),
nil,
)
if err != nil {
return nil, nil, err
}

var body schema.ActionListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
actions := make([]*Action, 0, len(body.Actions))
for _, i := range body.Actions {
actions = append(actions, ActionFromSchema(i))
}
return actions, resp, nil
}

// All returns all actions for the given options.
func (c *ResourceActionClient) All(ctx context.Context, opts ActionListOpts) ([]*Action, error) {
allActions := []*Action{}

err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
actions, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allActions = append(allActions, actions...)
return resp, nil
})
if err != nil {
return nil, err
}

return allActions, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type CertificateCreateResult struct {
// CertificateClient is a client for the Certificates API.
type CertificateClient struct {
client *Client
Action *ResourceActionClient
}

// GetByID retrieves a Certificate by its ID. If the Certificate does not exist, nil is returned.
Expand Down Expand Up @@ -182,7 +183,7 @@ func (c *CertificateClient) All(ctx context.Context) ([]*Certificate, error) {

// AllWithOpts returns all Certificates for the given options.
func (c *CertificateClient) AllWithOpts(ctx context.Context, opts CertificateListOpts) ([]*Certificate, error) {
var allCertificates []*Certificate
allCertificates := []*Certificate{}

err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
Expand Down
32 changes: 12 additions & 20 deletions cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func WithPollInterval(pollInterval time.Duration) ClientOption {
// function when polling from the API.
func WithPollBackoffFunc(f BackoffFunc) ClientOption {
return func(client *Client) {
client.backoffFunc = f
client.pollBackoffFunc = f
}
}

Expand Down Expand Up @@ -189,25 +189,25 @@ func NewClient(options ...ClientOption) *Client {
client.httpClient.Transport = i.InstrumentedRoundTripper()
}

client.Action = ActionClient{client: client}
client.Action = ActionClient{action: &ResourceActionClient{client: client}}
client.Datacenter = DatacenterClient{client: client}
client.FloatingIP = FloatingIPClient{client: client}
client.Image = ImageClient{client: client}
client.FloatingIP = FloatingIPClient{client: client, Action: &ResourceActionClient{client: client, resource: "floating_ips"}}
client.Image = ImageClient{client: client, Action: &ResourceActionClient{client: client, resource: "images"}}
client.ISO = ISOClient{client: client}
client.Location = LocationClient{client: client}
client.Network = NetworkClient{client: client}
client.Network = NetworkClient{client: client, Action: &ResourceActionClient{client: client, resource: "networks"}}
client.Pricing = PricingClient{client: client}
client.Server = ServerClient{client: client}
client.Server = ServerClient{client: client, Action: &ResourceActionClient{client: client, resource: "servers"}}
client.ServerType = ServerTypeClient{client: client}
client.SSHKey = SSHKeyClient{client: client}
client.Volume = VolumeClient{client: client}
client.LoadBalancer = LoadBalancerClient{client: client}
client.Volume = VolumeClient{client: client, Action: &ResourceActionClient{client: client, resource: "volumes"}}
client.LoadBalancer = LoadBalancerClient{client: client, Action: &ResourceActionClient{client: client, resource: "load_balancers"}}
client.LoadBalancerType = LoadBalancerTypeClient{client: client}
client.Certificate = CertificateClient{client: client}
client.Firewall = FirewallClient{client: client}
client.Certificate = CertificateClient{client: client, Action: &ResourceActionClient{client: client, resource: "certificates"}}
client.Firewall = FirewallClient{client: client, Action: &ResourceActionClient{client: client, resource: "firewalls"}}
client.PlacementGroup = PlacementGroupClient{client: client}
client.RDNS = RDNSClient{client: client}
client.PrimaryIP = PrimaryIPClient{client: client}
client.PrimaryIP = PrimaryIPClient{client: client, Action: &ResourceActionClient{client: client, resource: "primary_ips"}}

return client
}
Expand Down Expand Up @@ -290,7 +290,7 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
err = errorFromResponse(resp, body)
if err == nil {
err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
} else if isConflict(err) {
} else if IsError(err, ErrorCodeConflict) {
c.backoff(retries)
retries++
continue
Expand All @@ -309,14 +309,6 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
}
}

func isConflict(error error) bool {
err, ok := error.(Error)
if !ok {
return false
}
return err.Code == ErrorCodeConflict
}

func (c *Client) backoff(retries int) {
time.Sleep(c.backoffFunc(retries))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (c *DatacenterClient) All(ctx context.Context) ([]*Datacenter, error) {

// AllWithOpts returns all datacenters for the given options.
func (c *DatacenterClient) AllWithOpts(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, error) {
var allDatacenters []*Datacenter
allDatacenters := []*Datacenter{}

err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
Expand Down
Loading

0 comments on commit 225fc33

Please sign in to comment.