From 52ededcbb241fde4ff2542b57463a020b4e96b46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 00:52:49 +0000 Subject: [PATCH 1/2] =?UTF-8?q?chore(deps):=20Update=20module=20github.com?= =?UTF-8?q?/avast/retry-go/v4=20(v4.7.0=20=E2=86=92=20v5.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | datasource | package | from | to | | ---------- | ---------------------------- | ------ | ------ | | go | github.com/avast/retry-go/v4 | v4.7.0 | v5.0.0 | --- go.mod | 2 +- go.sum | 4 ++-- proxmox/api/client.go | 2 +- proxmox/cluster/id_generator.go | 2 +- proxmox/nodes/containers/containers.go | 2 +- proxmox/nodes/network.go | 2 +- proxmox/nodes/storage/content.go | 2 +- proxmox/nodes/tasks/tasks.go | 2 +- proxmox/nodes/vms/vms.go | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 34c298ce9..fcc7ca257 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ tool github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs require ( github.com/Microsoft/go-winio v0.6.2 - github.com/avast/retry-go/v4 v4.7.0 + github.com/avast/retry-go/v5 v5.0.0 github.com/brianvoe/gofakeit/v7 v7.12.0 github.com/google/go-cmp v0.7.0 github.com/google/go-querystring v1.1.0 diff --git a/go.sum b/go.sum index ae2ab1be8..b2be3dd7a 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avast/retry-go/v4 v4.7.0 h1:yjDs35SlGvKwRNSykujfjdMxMhMQQM0TnIjJaHB+Zio= -github.com/avast/retry-go/v4 v4.7.0/go.mod h1:ZMPDa3sY2bKgpLtap9JRUgk2yTAba7cgiFhqxY2Sg6Q= +github.com/avast/retry-go/v5 v5.0.0 h1:kf1Qc2UsTZ4qq8elDymqfbISvkyMuhgRxuJqX2NHP7k= +github.com/avast/retry-go/v5 v5.0.0/go.mod h1://d+usmKWio1agtZfS1H/ltTqwtIfBnRq9zEwjc3eH8= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmatcuk/doublestar/v4 v4.9.1 h1:X8jg9rRZmJd4yRy7ZeNDRnM+T3ZfHv15JiBJ/avrEXE= diff --git a/proxmox/api/client.go b/proxmox/api/client.go index 66145f040..f54541fad 100644 --- a/proxmox/api/client.go +++ b/proxmox/api/client.go @@ -21,7 +21,7 @@ import ( "sort" "strings" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/google/go-querystring/query" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" diff --git a/proxmox/cluster/id_generator.go b/proxmox/cluster/id_generator.go index bede42677..48532b0b2 100644 --- a/proxmox/cluster/id_generator.go +++ b/proxmox/cluster/id_generator.go @@ -19,7 +19,7 @@ import ( "strings" "time" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/rogpeppe/go-internal/lockedfile" "github.com/bpg/terraform-provider-proxmox/proxmox/api" diff --git a/proxmox/nodes/containers/containers.go b/proxmox/nodes/containers/containers.go index 239209919..2e605cd8b 100644 --- a/proxmox/nodes/containers/containers.go +++ b/proxmox/nodes/containers/containers.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/utils/ip" diff --git a/proxmox/nodes/network.go b/proxmox/nodes/network.go index 7fe363d74..af955f16e 100644 --- a/proxmox/nodes/network.go +++ b/proxmox/nodes/network.go @@ -16,7 +16,7 @@ import ( "sync" "time" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/bpg/terraform-provider-proxmox/proxmox/api" ) diff --git a/proxmox/nodes/storage/content.go b/proxmox/nodes/storage/content.go index d1931152b..dcdec8a5f 100644 --- a/proxmox/nodes/storage/content.go +++ b/proxmox/nodes/storage/content.go @@ -14,7 +14,7 @@ import ( "net/url" "sort" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/bpg/terraform-provider-proxmox/proxmox/api" ) diff --git a/proxmox/nodes/tasks/tasks.go b/proxmox/nodes/tasks/tasks.go index b71a5525a..379b8dcbe 100644 --- a/proxmox/nodes/tasks/tasks.go +++ b/proxmox/nodes/tasks/tasks.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/bpg/terraform-provider-proxmox/proxmox/api" ) diff --git a/proxmox/nodes/vms/vms.go b/proxmox/nodes/vms/vms.go index 5b9376e98..389340480 100644 --- a/proxmox/nodes/vms/vms.go +++ b/proxmox/nodes/vms/vms.go @@ -16,7 +16,7 @@ import ( "strings" "time" - "github.com/avast/retry-go/v4" + "github.com/avast/retry-go/v5" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/bpg/terraform-provider-proxmox/proxmox/api" From bcd2dbde1b1431ec60f36d321ba16b36a3cdb866 Mon Sep 17 00:00:00 2001 From: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Date: Sun, 30 Nov 2025 20:04:18 -0500 Subject: [PATCH 2/2] migrate API calls v4 -> v5 Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --- proxmox/api/client.go | 9 +- proxmox/cluster/id_generator.go | 28 ++--- proxmox/nodes/containers/containers.go | 73 ++++++------ proxmox/nodes/network.go | 15 +-- proxmox/nodes/storage/content.go | 18 +-- proxmox/nodes/tasks/tasks.go | 27 ++--- proxmox/nodes/vms/vms.go | 151 +++++++++++++------------ 7 files changed, 170 insertions(+), 151 deletions(-) diff --git a/proxmox/api/client.go b/proxmox/api/client.go index f54541fad..2b8b9f854 100644 --- a/proxmox/api/client.go +++ b/proxmox/api/client.go @@ -234,10 +234,7 @@ func (c *client) DoRequest( } //nolint:bodyclose - res, err := retry.DoWithData( - func() (*http.Response, error) { - return c.conn.httpClient.Do(req) - }, + res, err := retry.NewWithData[*http.Response]( retry.Context(ctx), retry.RetryIf(func(err error) bool { var urlErr *url.Error @@ -249,6 +246,10 @@ func (c *client) DoRequest( }), retry.LastErrorOnly(true), retry.Attempts(3), + ).Do( + func() (*http.Response, error) { + return c.conn.httpClient.Do(req) + }, ) if err != nil { return fmt.Errorf("failed to perform HTTP %s request (path: %s) - Reason: %w", diff --git a/proxmox/cluster/id_generator.go b/proxmox/cluster/id_generator.go index 48532b0b2..80acabd50 100644 --- a/proxmox/cluster/id_generator.go +++ b/proxmox/cluster/id_generator.go @@ -97,19 +97,7 @@ func (g IDGenerator) NextID(ctx context.Context) (int, error) { var errs []error - id, err := retry.DoWithData(func() (*int, error) { - if g.config.RandomIDs { - //nolint:gosec - newID = ptr.Ptr(rand.Intn(g.config.RandomIDEnd-g.config.RandomIDStat) + g.config.RandomIDStat) - } else if newID == nil { - newID, err = nextSequentialID(g.config.seqFName) - if err != nil { - return nil, err - } - } - - return g.client.GetNextID(ctx, newID) - }, + id, err := retry.NewWithData[*int]( retry.OnRetry(func(_ uint, err error) { if strings.Contains(err.Error(), "already exists") && newID != nil { newID, err = g.client.GetNextID(ctx, nil) @@ -125,6 +113,20 @@ func (g IDGenerator) NextID(ctx context.Context) (int, error) { retry.UntilSucceeded(), retry.DelayType(retry.FixedDelay), retry.Delay(200*time.Millisecond), + ).Do( + func() (*int, error) { + if g.config.RandomIDs { + //nolint:gosec + newID = ptr.Ptr(rand.Intn(g.config.RandomIDEnd-g.config.RandomIDStat) + g.config.RandomIDStat) + } else if newID == nil { + newID, err = nextSequentialID(g.config.seqFName) + if err != nil { + return nil, err + } + } + + return g.client.GetNextID(ctx, newID) + }, ) if err != nil { errs = append(errs, err) diff --git a/proxmox/nodes/containers/containers.go b/proxmox/nodes/containers/containers.go index 2e605cd8b..658013336 100644 --- a/proxmox/nodes/containers/containers.go +++ b/proxmox/nodes/containers/containers.go @@ -169,7 +169,26 @@ func (c *Client) WaitForContainerNetworkInterfaces( ctxWithTimeout, cancel := context.WithTimeout(ctx, timeout) defer cancel() - ifaces, err := retry.DoWithData( + ifaces, err := retry.NewWithData[[]GetNetworkInterfacesData]( + retry.Context(ctxWithTimeout), + retry.RetryIf(func(err error) bool { + var target *api.HTTPError + if errors.As(err, &target) { + if target.Code == http.StatusBadRequest { + // this is a special case to account for eventual consistency + // when creating a task -- the task may not be available via status API + // immediately after creation + return true + } + } + + return errors.Is(err, api.ErrNoDataObjectInResponse) || errors.Is(err, errNoIPsYet) + }), + retry.LastErrorOnly(true), + retry.UntilSucceeded(), + retry.DelayType(retry.FixedDelay), + retry.Delay(time.Second), + ).Do( func() ([]GetNetworkInterfacesData, error) { ifaces, err := c.GetContainerNetworkInterfaces(ctx) if err != nil { @@ -207,24 +226,6 @@ func (c *Client) WaitForContainerNetworkInterfaces( // all required IP types are available return ifaces, nil }, - retry.Context(ctxWithTimeout), - retry.RetryIf(func(err error) bool { - var target *api.HTTPError - if errors.As(err, &target) { - if target.Code == http.StatusBadRequest { - // this is a special case to account for eventual consistency - // when creating a task -- the task may not be available via status API - // immediately after creation - return true - } - } - - return errors.Is(err, api.ErrNoDataObjectInResponse) || errors.Is(err, errNoIPsYet) - }), - retry.LastErrorOnly(true), - retry.UntilSucceeded(), - retry.DelayType(retry.FixedDelay), - retry.Delay(time.Second), ) if errors.Is(err, context.DeadlineExceeded) { return nil, errors.New("timeout while waiting for container IP addresses") @@ -408,7 +409,15 @@ func (c *Client) WaitForContainerStatus(ctx context.Context, status string) erro unexpectedStatus := fmt.Errorf("unexpected status %q", status) - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.RetryIf(func(err error) bool { + return errors.Is(err, unexpectedStatus) + }), + retry.UntilSucceeded(), + retry.Delay(1*time.Second), + retry.LastErrorOnly(true), + ).Do( func() error { data, err := c.GetContainerStatus(ctx) if err != nil { @@ -421,13 +430,6 @@ func (c *Client) WaitForContainerStatus(ctx context.Context, status string) erro return nil }, - retry.Context(ctx), - retry.RetryIf(func(err error) bool { - return errors.Is(err, unexpectedStatus) - }), - retry.UntilSucceeded(), - retry.Delay(1*time.Second), - retry.LastErrorOnly(true), ) if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("timeout while waiting for container %d to enter the status %q", c.VMID, status) @@ -444,7 +446,15 @@ func (c *Client) WaitForContainerStatus(ctx context.Context, status string) erro func (c *Client) WaitForContainerConfigUnlock(ctx context.Context, ignoreErrorResponse bool) error { stillLocked := errors.New("still locked") - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.RetryIf(func(err error) bool { + return errors.Is(err, stillLocked) || ignoreErrorResponse + }), + retry.UntilSucceeded(), + retry.Delay(1*time.Second), + retry.LastErrorOnly(true), + ).Do( func() error { data, err := c.GetContainerStatus(ctx) if err != nil { @@ -457,13 +467,6 @@ func (c *Client) WaitForContainerConfigUnlock(ctx context.Context, ignoreErrorRe return nil }, - retry.Context(ctx), - retry.RetryIf(func(err error) bool { - return errors.Is(err, stillLocked) || ignoreErrorResponse - }), - retry.UntilSucceeded(), - retry.Delay(1*time.Second), - retry.LastErrorOnly(true), ) if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("timeout while waiting for container %d configuration to become unlocked", c.VMID) diff --git a/proxmox/nodes/network.go b/proxmox/nodes/network.go index af955f16e..dbdc7a949 100644 --- a/proxmox/nodes/network.go +++ b/proxmox/nodes/network.go @@ -74,7 +74,14 @@ func (c *Client) ReloadNetworkConfiguration(ctx context.Context) error { resBody := &ReloadNetworkResponseBody{} - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.Delay(1*time.Second), + retry.Attempts(3), + retry.RetryIf(func(err error) bool { + return strings.Contains(err.Error(), "exit code 89") + }), + ).Do( func() error { err := c.DoRequest(ctx, http.MethodPut, c.ExpandPath("network"), nil, resBody) if err != nil { @@ -87,12 +94,6 @@ func (c *Client) ReloadNetworkConfiguration(ctx context.Context) error { return c.Tasks().WaitForTask(ctx, *resBody.Data) }, - retry.Context(ctx), - retry.Delay(1*time.Second), - retry.Attempts(3), - retry.RetryIf(func(err error) bool { - return strings.Contains(err.Error(), "exit code 89") - }), ) if err != nil { return fmt.Errorf("failed to reload network configuration for node \"%s\": %w", c.NodeName, err) diff --git a/proxmox/nodes/storage/content.go b/proxmox/nodes/storage/content.go index dcdec8a5f..bfff81913 100644 --- a/proxmox/nodes/storage/content.go +++ b/proxmox/nodes/storage/content.go @@ -26,10 +26,7 @@ func (c *Client) DeleteDatastoreFile( ) error { path := c.ExpandPath(fmt.Sprintf("content/%s", url.PathEscape(volumeID))) - err := retry.Do( - func() error { - return c.DoRequest(ctx, http.MethodDelete, path, nil, nil) - }, + err := retry.New( retry.Context(ctx), retry.RetryIf(func(err error) bool { var httpError *api.HTTPError @@ -40,6 +37,10 @@ func (c *Client) DeleteDatastoreFile( return !errors.Is(err, api.ErrResourceDoesNotExist) }), retry.LastErrorOnly(true), + ).Do( + func() error { + return c.DoRequest(ctx, http.MethodDelete, path, nil, nil) + }, ) if err != nil { return fmt.Errorf("error deleting file %s from datastore %s: %w", volumeID, c.StorageName, err) @@ -54,10 +55,7 @@ func (c *Client) ListDatastoreFiles( ) ([]*DatastoreFileListResponseData, error) { resBody := &DatastoreFileListResponseBody{} - err := retry.Do( - func() error { - return c.DoRequest(ctx, http.MethodGet, c.ExpandPath("content"), nil, resBody) - }, + err := retry.New( retry.Context(ctx), retry.RetryIf(func(err error) bool { var httpError *api.HTTPError @@ -68,6 +66,10 @@ func (c *Client) ListDatastoreFiles( return !errors.Is(err, api.ErrResourceDoesNotExist) }), retry.LastErrorOnly(true), + ).Do( + func() error { + return c.DoRequest(ctx, http.MethodGet, c.ExpandPath("content"), nil, resBody) + }, ) if err != nil { return nil, fmt.Errorf("error listing files from datastore %s: %w", c.StorageName, err) diff --git a/proxmox/nodes/tasks/tasks.go b/proxmox/nodes/tasks/tasks.go index 379b8dcbe..7b24e486a 100644 --- a/proxmox/nodes/tasks/tasks.go +++ b/proxmox/nodes/tasks/tasks.go @@ -132,19 +132,7 @@ func (c *Client) WaitForTask(ctx context.Context, upid string, opts ...TaskWaitO opt.apply(options) } - status, err := retry.DoWithData( - func() (*GetTaskStatusResponseData, error) { - status, err := c.GetTaskStatus(ctx, upid) - if err != nil { - return nil, err - } - - if status.Status == "running" { - return nil, errStillRunning - } - - return status, err - }, + status, err := retry.NewWithData[*GetTaskStatusResponseData]( retry.Context(ctx), retry.RetryIf(func(err error) bool { var target *api.HTTPError @@ -167,6 +155,19 @@ func (c *Client) WaitForTask(ctx context.Context, upid string, opts ...TaskWaitO retry.UntilSucceeded(), retry.DelayType(retry.FixedDelay), retry.Delay(time.Second), + ).Do( + func() (*GetTaskStatusResponseData, error) { + status, err := c.GetTaskStatus(ctx, upid) + if err != nil { + return nil, err + } + + if status.Status == "running" { + return nil, errStillRunning + } + + return status, err + }, ) if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("timeout while waiting for task %q to complete", upid) diff --git a/proxmox/nodes/vms/vms.go b/proxmox/nodes/vms/vms.go index 389340480..af3ca6144 100644 --- a/proxmox/nodes/vms/vms.go +++ b/proxmox/nodes/vms/vms.go @@ -35,7 +35,12 @@ func (c *Client) CloneVM(ctx context.Context, retries int, d *CloneRequestBody) retries = 1 } - err = retry.Do( + err = retry.New( + retry.Context(ctx), + retry.Attempts(uint(retries)), + retry.Delay(10*time.Second), + retry.LastErrorOnly(false), + ).Do( func() error { err = c.DoRequest(ctx, http.MethodPost, c.ExpandPath("clone"), d, resBody) if err != nil { @@ -49,10 +54,6 @@ func (c *Client) CloneVM(ctx context.Context, retries int, d *CloneRequestBody) // ignoring warnings as per https://www.mail-archive.com/pve-devel@lists.proxmox.com/msg17724.html return c.Tasks().WaitForTask(ctx, *resBody.Data, tasks.WithIgnoreWarnings()) }, - retry.Context(ctx), - retry.Attempts(uint(retries)), - retry.Delay(10*time.Second), - retry.LastErrorOnly(false), ) if err != nil { return fmt.Errorf("error waiting for VM clone: %w", err) @@ -103,15 +104,7 @@ func (c *Client) CreateVMAsync(ctx context.Context, d *CreateRequestBody) (*stri // retry the request if we get an error that the VM already exists // but only if we're retrying. If this error is returned by the first // request, we'll just return the error (i.e. can't "override" the VM). - err := retry.Do( - func() error { - err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, resBody) - if err != nil && retrying && strings.Contains(err.Error(), "already exists") { - return nil - } - - return err - }, + err := retry.New( retry.Context(ctx), retry.Attempts(3), retry.Delay(1*time.Second), @@ -127,6 +120,15 @@ func (c *Client) CreateVMAsync(ctx context.Context, d *CreateRequestBody) (*stri retry.RetryIf(func(err error) bool { return strings.Contains(err.Error(), "got no worker upid") }), + ).Do( + func() error { + err := c.DoRequest(ctx, http.MethodPost, c.basePath(), d, resBody) + if err != nil && retrying && strings.Contains(err.Error(), "already exists") { + return nil + } + + return err + }, ) if err != nil { return nil, fmt.Errorf("error creating VM: %w", err) @@ -169,11 +171,7 @@ func (c *Client) DeleteVMAsync(ctx context.Context, purge bool, destroyUnreferen destroyUnreferencedDisksValue = 1 } - err := retry.Do( - func() error { - path := fmt.Sprintf("?destroy-unreferenced-disks=%d&purge=%d", destroyUnreferencedDisksValue, purgeValue) - return c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(path), nil, resBody) - }, + err := retry.New( retry.Context(ctx), retry.Attempts(3), retry.Delay(1*time.Second), @@ -181,6 +179,11 @@ func (c *Client) DeleteVMAsync(ctx context.Context, purge bool, destroyUnreferen retry.RetryIf(func(err error) bool { return !errors.Is(err, api.ErrResourceDoesNotExist) }), + ).Do( + func() error { + path := fmt.Sprintf("?destroy-unreferenced-disks=%d&purge=%d", destroyUnreferencedDisksValue, purgeValue) + return c.DoRequest(ctx, http.MethodDelete, c.ExpandPath(path), nil, resBody) + }, ) if err != nil { return nil, fmt.Errorf("error deleting VM: %w", err) @@ -382,7 +385,15 @@ func (c *Client) RebootVMAsync(ctx context.Context, d *RebootRequestBody) (*stri // ResizeVMDisk resizes a virtual machine disk. func (c *Client) ResizeVMDisk(ctx context.Context, d *ResizeDiskRequestBody) error { - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.Attempts(3), + retry.Delay(1*time.Second), + retry.LastErrorOnly(false), + retry.RetryIf(func(err error) bool { + return strings.Contains(err.Error(), "got timeout") + }), + ).Do( func() error { taskID, err := c.ResizeVMDiskAsync(ctx, d) if err != nil { @@ -391,13 +402,6 @@ func (c *Client) ResizeVMDisk(ctx context.Context, d *ResizeDiskRequestBody) err return c.Tasks().WaitForTask(ctx, *taskID) }, - retry.Context(ctx), - retry.Attempts(3), - retry.Delay(1*time.Second), - retry.LastErrorOnly(false), - retry.RetryIf(func(err error) bool { - return strings.Contains(err.Error(), "got timeout") - }), ) if err != nil { return fmt.Errorf("error waiting for VM disk resize: %w", err) @@ -491,7 +495,15 @@ func (c *Client) StartVMAsync(ctx context.Context, timeoutSec int) (*string, err resBody := &StartResponseBody{} // PVE may return a 500 error "got no worker upid - start worker failed", so we retry few times. - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.Attempts(3), + retry.Delay(1*time.Second), + retry.LastErrorOnly(true), + retry.RetryIf(func(err error) bool { + return strings.Contains(err.Error(), "got no worker upid") + }), + ).Do( func() error { err := c.DoRequest(ctx, http.MethodPost, c.ExpandPath("status/start"), reqBody, resBody) if err != nil && strings.Contains(err.Error(), "already running") { @@ -500,13 +512,6 @@ func (c *Client) StartVMAsync(ctx context.Context, timeoutSec int) (*string, err return err }, - retry.Context(ctx), - retry.Attempts(3), - retry.Delay(1*time.Second), - retry.LastErrorOnly(true), - retry.RetryIf(func(err error) bool { - return strings.Contains(err.Error(), "got no worker upid") - }), ) if err != nil { return nil, fmt.Errorf("error starting VM: %w", err) @@ -552,10 +557,7 @@ func (c *Client) StopVMAsync(ctx context.Context) (*string, error) { // UpdateVM updates a virtual machine. func (c *Client) UpdateVM(ctx context.Context, d *UpdateRequestBody) error { - err := retry.Do( - func() error { - return c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil) - }, + err := retry.New( retry.Context(ctx), retry.Attempts(3), retry.Delay(1*time.Second), @@ -563,6 +565,10 @@ func (c *Client) UpdateVM(ctx context.Context, d *UpdateRequestBody) error { retry.RetryIf(func(err error) bool { return strings.Contains(err.Error(), "got timeout") }), + ).Do( + func() error { + return c.DoRequest(ctx, http.MethodPut, c.ExpandPath("config"), d, nil) + }, ) if err != nil { return fmt.Errorf("error updating VM: %w", err) @@ -613,7 +619,23 @@ func (c *Client) WaitForNetworkInterfacesFromVMAgent( } }() - data, err := retry.DoWithData( + data, err := retry.NewWithData[*GetQEMUNetworkInterfacesResponseData]( + retry.Context(ctxWithTimeout), + retry.RetryIf(func(err error) bool { + var target *api.HTTPError + if errors.As(err, &target) { + if target.Code == http.StatusBadRequest { + return true + } + } + + return errors.Is(err, errNoIPsYet) + }), + retry.LastErrorOnly(true), + retry.UntilSucceeded(), + retry.DelayType(retry.FixedDelay), + retry.Delay(time.Second), + ).Do( func() (*GetQEMUNetworkInterfacesResponseData, error) { data, err := c.GetVMNetworkInterfacesFromAgent(ctx) if err != nil { @@ -666,21 +688,6 @@ func (c *Client) WaitForNetworkInterfacesFromVMAgent( // all required IP types are available return data, nil }, - retry.Context(ctxWithTimeout), - retry.RetryIf(func(err error) bool { - var target *api.HTTPError - if errors.As(err, &target) { - if target.Code == http.StatusBadRequest { - return true - } - } - - return errors.Is(err, errNoIPsYet) - }), - retry.LastErrorOnly(true), - retry.UntilSucceeded(), - retry.DelayType(retry.FixedDelay), - retry.Delay(time.Second), ) if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) { @@ -733,7 +740,15 @@ func (c *Client) checkIPAddresses( func (c *Client) WaitForVMConfigUnlock(ctx context.Context, ignoreErrorResponse bool) error { stillLocked := errors.New("still locked") - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.UntilSucceeded(), + retry.Delay(1*time.Second), + retry.LastErrorOnly(true), + retry.RetryIf(func(err error) bool { + return errors.Is(err, stillLocked) || ignoreErrorResponse + }), + ).Do( func() error { data, err := c.GetVMStatus(ctx) if err != nil { @@ -746,13 +761,6 @@ func (c *Client) WaitForVMConfigUnlock(ctx context.Context, ignoreErrorResponse return nil }, - retry.Context(ctx), - retry.UntilSucceeded(), - retry.Delay(1*time.Second), - retry.LastErrorOnly(true), - retry.RetryIf(func(err error) bool { - return errors.Is(err, stillLocked) || ignoreErrorResponse - }), ) if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("timeout while waiting for VM %d configuration to become unlocked", c.VMID) @@ -771,7 +779,15 @@ func (c *Client) WaitForVMStatus(ctx context.Context, status string) error { unexpectedStatus := fmt.Errorf("unexpected status %q", status) - err := retry.Do( + err := retry.New( + retry.Context(ctx), + retry.UntilSucceeded(), + retry.Delay(1*time.Second), + retry.LastErrorOnly(true), + retry.RetryIf(func(err error) bool { + return errors.Is(err, unexpectedStatus) + }), + ).Do( func() error { data, err := c.GetVMStatus(ctx) if err != nil { @@ -784,13 +800,6 @@ func (c *Client) WaitForVMStatus(ctx context.Context, status string) error { return nil }, - retry.Context(ctx), - retry.UntilSucceeded(), - retry.Delay(1*time.Second), - retry.LastErrorOnly(true), - retry.RetryIf(func(err error) bool { - return errors.Is(err, unexpectedStatus) - }), ) if errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("timeout while waiting for VM %d to enter the status %q", c.VMID, status)