From e20c92be9f54fd7324b324d7c6a4ef916c3b35a9 Mon Sep 17 00:00:00 2001 From: Matt White <16320656+matt-FFFFFF@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:06:49 +0000 Subject: [PATCH] fix(retry): add WithMaxElapsedTime to retry options and refactor to use functional options --- internal/clients/data_plane_client_test.go | 56 +++++++++++++--- internal/clients/resource_client.go | 38 +++++++---- internal/clients/resource_client_test.go | 56 +++++++++++++--- internal/retry/retryable_errors.go | 9 +++ .../services/azapi_data_plane_resource.go | 55 +++++++++------- internal/services/azapi_resource.go | 65 ++++++++++--------- .../azapi_resource_action_data_source.go | 14 ++-- .../azapi_resource_action_ephemeral.go | 14 ++-- .../azapi_resource_action_resource.go | 14 ++-- .../services/azapi_resource_data_source.go | 14 ++-- .../azapi_resource_list_data_source.go | 14 ++-- internal/services/azapi_update_resource.go | 27 ++++---- 12 files changed, 244 insertions(+), 132 deletions(-) diff --git a/internal/clients/data_plane_client_test.go b/internal/clients/data_plane_client_test.go index 3f08014ea..e89f63d45 100644 --- a/internal/clients/data_plane_client_test.go +++ b/internal/clients/data_plane_client_test.go @@ -8,6 +8,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/clients" "github.com/Azure/terraform-provider-azapi/internal/services/parse" + "github.com/cenkalti/backoff/v4" "github.com/stretchr/testify/assert" ) @@ -27,9 +28,16 @@ import ( // // We check these timings are expected using the assert.InDeltaSlice function. func TestRetryDataPlaneClient(t *testing.T) { + t.Parallel() mock := NewMockDataPlaneClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 30, 2, 0.0, []string{"retry error"}) - retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(30*time.Second), + backoff.WithMultiplier(2), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"retry error"}) + retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), parse.DataPlaneResourceId{}, clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.requestCount) @@ -47,36 +55,64 @@ func TestRetryDataPlaneClient(t *testing.T) { } func TestRetryDataPlaneClientRegexp(t *testing.T) { + t.Parallel() mock := NewMockDataPlaneClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"^retry"}) - retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"^retry"}) + retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), parse.DataPlaneResourceId{}, clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.RequestCount()) } func TestRetryDataPlaneClientMultiRegexp(t *testing.T) { + t.Parallel() mock := NewMockDataPlaneClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"nomatch", "^retry"}) - retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"nomatch", "^retry"}) + retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), parse.DataPlaneResourceId{}, clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.RequestCount()) } func TestRetryDataPlaneClientMultiRegexpNoMatchWithPermError(t *testing.T) { + t.Parallel() mock := NewMockDataPlaneClient(t, nil, errors.New("perm error"), 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"retry"}) - retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"retry"}) + retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), parse.DataPlaneResourceId{}, clients.DefaultRequestOptions()) assert.ErrorContains(t, err, "perm error") assert.Equal(t, 3, mock.RequestCount()) } func TestRetryDataPlaneClientContextDeadline(t *testing.T) { + t.Parallel() mock := NewMockDataPlaneClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(60, 60, 1.5, 0.0, []string{"^retry"}) - retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(60*time.Second), + backoff.WithMaxInterval(60*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"^retry"}) + retryClient := clients.NewDataPlaneClientRetryableErrors(mock, bkof, rexs, nil, nil) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() start := time.Now() diff --git a/internal/clients/resource_client.go b/internal/clients/resource_client.go index 2e82d46ec..5f041dc53 100644 --- a/internal/clients/resource_client.go +++ b/internal/clients/resource_client.go @@ -84,20 +84,30 @@ func NewResourceClient(credential azcore.TokenCredential, opt *arm.ClientOptions }, nil } -// NewRetryableErrors creates the backoff and error regexs for retryable errors. -func NewRetryableErrors(intervalSeconds, maxIntervalSeconds int, multiplier, randomizationFactor float64, errorRegexs []string) (*backoff.ExponentialBackOff, []regexp.Regexp) { - bkof := backoff.NewExponentialBackOff( - backoff.WithInitialInterval(time.Duration(intervalSeconds)*time.Second), - backoff.WithRandomizationFactor(randomizationFactor), - backoff.WithMaxInterval(time.Duration(maxIntervalSeconds)*time.Second), - backoff.WithRandomizationFactor(randomizationFactor), - backoff.WithMultiplier(multiplier), - ) - res := make([]regexp.Regexp, len(errorRegexs)) - for i, e := range errorRegexs { - res[i] = *regexp.MustCompile(e) // MustCompile as schema has custom validation so we know it's valid - } - return bkof, res +// // NewRetryableErrors creates the backoff and error regexs for retryable errors. +// func NewRetryableErrors(intervalSeconds, maxIntervalSeconds int, multiplier, randomizationFactor float64, errorRegexs []string) (*backoff.ExponentialBackOff, []regexp.Regexp) { +// bkof := backoff.NewExponentialBackOff( +// backoff.WithInitialInterval(time.Duration(intervalSeconds)*time.Second), +// backoff.WithRandomizationFactor(randomizationFactor), +// backoff.WithMaxInterval(time.Duration(maxIntervalSeconds)*time.Second), +// backoff.WithRandomizationFactor(randomizationFactor), +// backoff.WithMultiplier(multiplier), +// ) +// res := make([]regexp.Regexp, len(errorRegexs)) +// for i, e := range errorRegexs { +// res[i] = *regexp.MustCompile(e) // MustCompile as schema has custom validation so we know it's valid +// } +// return bkof, res +// } + +// StringSliceToRegexpSliceMust converts a slice of strings to a slice of regexps. +// It panics if any of the strings are invalid regexps. +func StringSliceToRegexpSliceMust(ss []string) []regexp.Regexp { + res := make([]regexp.Regexp, len(ss)) + for i, e := range ss { + res[i] = *regexp.MustCompile(e) + } + return res } // WithRetry configures the retryable errors for the client. diff --git a/internal/clients/resource_client_test.go b/internal/clients/resource_client_test.go index 5aaa79632..4ea22d366 100644 --- a/internal/clients/resource_client_test.go +++ b/internal/clients/resource_client_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/Azure/terraform-provider-azapi/internal/clients" + "github.com/cenkalti/backoff/v4" "github.com/stretchr/testify/assert" ) @@ -26,9 +27,16 @@ import ( // // We check these timings are expected using the assert.InDeltaSlice function. func TestRetryClient(t *testing.T) { + t.Parallel() mock := NewMockResourceClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 30, 2, 0.0, []string{"retry error"}) - retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + rexs := clients.StringSliceToRegexpSliceMust([]string{"retry error"}) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(30*time.Second), + backoff.WithMultiplier(2), + backoff.WithRandomizationFactor(0.0), + ) + retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), "", "", clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.requestCount) @@ -46,36 +54,64 @@ func TestRetryClient(t *testing.T) { } func TestRetryClientRegexp(t *testing.T) { + t.Parallel() mock := NewMockResourceClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"^retry"}) - retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + rexs := clients.StringSliceToRegexpSliceMust([]string{"^retry"}) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), "", "", clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.RequestCount()) } func TestRetryClientMultiRegexp(t *testing.T) { + t.Parallel() mock := NewMockResourceClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"nomatch", "^retry"}) - retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + rexs := clients.StringSliceToRegexpSliceMust([]string{"nomatch", "^retry"}) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), "", "", clients.DefaultRequestOptions()) assert.NoError(t, err) assert.Equal(t, 3, mock.RequestCount()) } func TestRetryClientMultiRegexpNoMatchWithPermError(t *testing.T) { + t.Parallel() mock := NewMockResourceClient(t, nil, errors.New("perm error"), 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(1, 5, 1.5, 0.0, []string{"retry"}) - retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + rexs := clients.StringSliceToRegexpSliceMust([]string{"retry"}) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(1*time.Second), + backoff.WithMaxInterval(5*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, rexs, nil, nil) _, err := retryClient.Get(context.Background(), "", "", clients.DefaultRequestOptions()) assert.ErrorContains(t, err, "perm error") assert.Equal(t, 3, mock.RequestCount()) } func TestRetryClientContextDeadline(t *testing.T) { + t.Parallel() mock := NewMockResourceClient(t, nil, nil, 3, errors.New("retry error")) - bkof, errRegExps := clients.NewRetryableErrors(60, 60, 1.5, 0.0, []string{"^retry"}) - retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, errRegExps, nil, nil) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(60*time.Second), + backoff.WithMaxInterval(60*time.Second), + backoff.WithMultiplier(1.5), + backoff.WithRandomizationFactor(0.0), + ) + rexs := clients.StringSliceToRegexpSliceMust([]string{"^retry"}) + retryClient := clients.NewResourceClientRetryableErrors(mock, bkof, rexs, nil, nil) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() start := time.Now() diff --git a/internal/retry/retryable_errors.go b/internal/retry/retryable_errors.go index f9d7429bd..856a922bc 100644 --- a/internal/retry/retryable_errors.go +++ b/internal/retry/retryable_errors.go @@ -6,6 +6,7 @@ import ( "math/big" "regexp" "strings" + "time" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -606,10 +607,18 @@ func (v RetryValue) GetIntervalSeconds() int { return v.getInt64AttrValue(intervalSecondsAttributeName) } +func (v RetryValue) GetIntervalSecondsAsDuration() time.Duration { + return time.Duration(v.IntervalSeconds.ValueInt64()) * time.Second +} + func (v RetryValue) GetMaxIntervalSeconds() int { return v.getInt64AttrValue(maxIntervalSecondsAttributeName) } +func (v RetryValue) GetMaxIntervalSecondsAsDuration() time.Duration { + return time.Duration(v.MaxIntervalSeconds.ValueInt64()) * time.Second +} + func (v RetryValue) GetMultiplier() float64 { return v.getNumberAttrValue(multiplierAttributeName) } diff --git a/internal/services/azapi_data_plane_resource.go b/internal/services/azapi_data_plane_resource.go index bcd0faaf2..cfe004024 100644 --- a/internal/services/azapi_data_plane_resource.go +++ b/internal/services/azapi_data_plane_resource.go @@ -374,19 +374,6 @@ func (r *DataPlaneResource) CreateUpdate(ctx context.Context, plan tfsdk.Plan, s ctx = tflog.SetField(ctx, "resource_id", id.ID()) - var client clients.DataPlaneRequester - client = r.ProviderData.DataPlaneClient - if !model.Retry.IsNull() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), - ) - tflog.Debug(ctx, "azapi_data_plane_resource.CreateUpdate is using retry") - client = r.ProviderData.DataPlaneClient.WithRetry(bkof, regexps, nil, nil) - } isNewResource := state == nil || state.Raw.IsNull() var timeout time.Duration @@ -402,6 +389,22 @@ func (r *DataPlaneResource) CreateUpdate(ctx context.Context, plan tfsdk.Plan, s return } } + + var client clients.DataPlaneRequester + client = r.ProviderData.DataPlaneClient + if !model.Retry.IsNull() { + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(timeout), + ) + tflog.Debug(ctx, "azapi_data_plane_resource.CreateUpdate is using retry") + client = r.ProviderData.DataPlaneClient.WithRetry(bkof, regexps, nil, nil) + } + ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -500,12 +503,13 @@ func (r *DataPlaneResource) Read(ctx context.Context, request resource.ReadReque var client clients.DataPlaneRequester client = r.ProviderData.DataPlaneClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "azapi_data_plane_resource.Read is using retry") client = r.ProviderData.DataPlaneClient.WithRetry(bkof, regexps, nil, nil) @@ -592,12 +596,13 @@ func (r *DataPlaneResource) Delete(ctx context.Context, request resource.DeleteR var client clients.DataPlaneRequester client = r.ProviderData.DataPlaneClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(deleteTimeout), ) tflog.Debug(ctx, "azapi_data_plane_resource.Delete is using retry") client = r.ProviderData.DataPlaneClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_resource.go b/internal/services/azapi_resource.go index 6c79c8e7b..51c83bbfe 100644 --- a/internal/services/azapi_resource.go +++ b/internal/services/azapi_resource.go @@ -607,19 +607,6 @@ func (r *AzapiResource) CreateUpdate(ctx context.Context, requestPlan tfsdk.Plan ctx = tflog.SetField(ctx, "resource_id", id.ID()) - var client clients.Requester - client = r.ProviderData.ResourceClient - if !plan.Retry.IsNull() { - bkof, regexps := clients.NewRetryableErrors( - plan.Retry.GetIntervalSeconds(), - plan.Retry.GetMaxIntervalSeconds(), - plan.Retry.GetMultiplier(), - plan.Retry.GetRandomizationFactor(), - plan.Retry.GetErrorMessages(), - ) - tflog.Debug(ctx, "azapi_resource.CreateUpdate is using retry") - client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) - } isNewResource := responseState == nil || responseState.Raw.IsNull() ctx = tflog.SetField(ctx, "is_new_resource", isNewResource) var timeout time.Duration @@ -635,6 +622,20 @@ func (r *AzapiResource) CreateUpdate(ctx context.Context, requestPlan tfsdk.Plan return } } + var client clients.Requester + client = r.ProviderData.ResourceClient + if !plan.Retry.IsNull() { + regexps := clients.StringSliceToRegexpSliceMust(plan.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(plan.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(plan.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(plan.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(plan.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(timeout), + ) + tflog.Debug(ctx, "azapi_resource.CreateUpdate is using retry") + client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) + } ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() @@ -810,12 +811,13 @@ func (r *AzapiResource) Read(ctx context.Context, request resource.ReadRequest, var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "azapi_resource.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) @@ -948,26 +950,27 @@ func (r *AzapiResource) Delete(ctx context.Context, request resource.DeleteReque ctx = tflog.SetField(ctx, "resource_id", id.ID()) + deleteTimeout, diags := model.Timeouts.Delete(ctx, 30*time.Minute) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(deleteTimeout), ) tflog.Debug(ctx, "azapi_resource.Delete is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) } - deleteTimeout, diags := model.Timeouts.Delete(ctx, 30*time.Minute) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - ctx, cancel := context.WithTimeout(ctx, deleteTimeout) defer cancel() diff --git a/internal/services/azapi_resource_action_data_source.go b/internal/services/azapi_resource_action_data_source.go index ee8fbd4cc..3383e43da 100644 --- a/internal/services/azapi_resource_action_data_source.go +++ b/internal/services/azapi_resource_action_data_source.go @@ -10,6 +10,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/retry" "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" @@ -185,12 +186,13 @@ func (r *ResourceActionDataSource) Read(ctx context.Context, request datasource. var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "data.azapi_resource_action.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_resource_action_ephemeral.go b/internal/services/azapi_resource_action_ephemeral.go index afbccd849..a76be6eaa 100644 --- a/internal/services/azapi_resource_action_ephemeral.go +++ b/internal/services/azapi_resource_action_ephemeral.go @@ -12,6 +12,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/retry" "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -191,12 +192,13 @@ func (r *ActionEphemeral) Open(ctx context.Context, request ephemeral.OpenReques var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "azapi_resource_action.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_resource_action_resource.go b/internal/services/azapi_resource_action_resource.go index a71865c85..be1dd1924 100644 --- a/internal/services/azapi_resource_action_resource.go +++ b/internal/services/azapi_resource_action_resource.go @@ -16,6 +16,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/services/myplanmodifier" "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -354,12 +355,13 @@ func (r *ActionResource) Action(ctx context.Context, model ActionResourceModel, var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(actionTimeout), ) tflog.Debug(ctx, "azapi_resource_action.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_resource_data_source.go b/internal/services/azapi_resource_data_source.go index 9972d86a2..35228ff16 100644 --- a/internal/services/azapi_resource_data_source.go +++ b/internal/services/azapi_resource_data_source.go @@ -16,6 +16,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" "github.com/Azure/terraform-provider-azapi/utils" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -260,12 +261,13 @@ func (r *AzapiResourceDataSource) Read(ctx context.Context, request datasource.R var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "data.azapi_resource.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_resource_list_data_source.go b/internal/services/azapi_resource_list_data_source.go index 302dc0723..09ea50112 100644 --- a/internal/services/azapi_resource_list_data_source.go +++ b/internal/services/azapi_resource_list_data_source.go @@ -12,6 +12,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" "github.com/Azure/terraform-provider-azapi/utils" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -139,12 +140,13 @@ func (r *ResourceListDataSource) Read(ctx context.Context, request datasource.Re var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) tflog.Debug(ctx, "data.azapi_resource_list.Read is using retry") client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) diff --git a/internal/services/azapi_update_resource.go b/internal/services/azapi_update_resource.go index c03197659..554a5bb82 100644 --- a/internal/services/azapi_update_resource.go +++ b/internal/services/azapi_update_resource.go @@ -18,6 +18,7 @@ import ( "github.com/Azure/terraform-provider-azapi/internal/services/myvalidator" "github.com/Azure/terraform-provider-azapi/internal/services/parse" "github.com/Azure/terraform-provider-azapi/utils" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -353,12 +354,13 @@ func (r *AzapiUpdateResource) CreateUpdate(ctx context.Context, plan tfsdk.Plan, var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(timeout), ) client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) } @@ -454,12 +456,13 @@ func (r *AzapiUpdateResource) Read(ctx context.Context, request resource.ReadReq var client clients.Requester client = r.ProviderData.ResourceClient if !model.Retry.IsNull() && !model.Retry.IsUnknown() { - bkof, regexps := clients.NewRetryableErrors( - model.Retry.GetIntervalSeconds(), - model.Retry.GetMaxIntervalSeconds(), - model.Retry.GetMultiplier(), - model.Retry.GetRandomizationFactor(), - model.Retry.GetErrorMessages(), + regexps := clients.StringSliceToRegexpSliceMust(model.Retry.GetErrorMessages()) + bkof := backoff.NewExponentialBackOff( + backoff.WithInitialInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMaxInterval(model.Retry.GetMaxIntervalSecondsAsDuration()), + backoff.WithMultiplier(model.Retry.GetMultiplier()), + backoff.WithRandomizationFactor(model.Retry.GetRandomizationFactor()), + backoff.WithMaxElapsedTime(readTimeout), ) client = r.ProviderData.ResourceClient.WithRetry(bkof, regexps, nil, nil) }