@@ -9,28 +9,44 @@ import (
99 "time"
1010
1111 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
12+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
1213 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1314)
1415
16+ // sdkRetryTimeout is large on purpose: we stop with max_retries inside RetryFunc,
17+ // not with the SDK helper's timeout clock.
18+ const sdkRetryTimeout = 24 * time .Hour
19+
20+ // default config values
1521const (
1622 resourceRetryDefaultIntervalSeconds = 10.0
1723 resourceRetryDefaultMaxIntervalSeconds = 180.0
1824 resourceRetryDefaultMultiplier = 1.5
1925)
2026
21- // ResourceRetrySchema is the optional resource-level retry block (after provider HTTP retry).
27+ // defaultResourceRetryErrorPatterns mirrors provider-level transient/rate-limit text defaults.
28+ var defaultResourceRetryErrorPatterns = []string {
29+ "Too Many Requests" ,
30+ "will be released in" ,
31+ "EOF" ,
32+ "connection reset by peer" ,
33+ "connection refused" ,
34+ }
35+
36+ // ResourceRetrySchema is the optional resource-level retry block.
37+ // When set, it overrides provider HTTP retry for that resource's CRUD calls.
2238func ResourceRetrySchema () * schema.Schema {
2339 return & schema.Schema {
2440 Type : schema .TypeList ,
2541 Optional : true ,
2642 MaxItems : 1 ,
27- Description : "Optional resource-level retry. Runs after provider HTTP retries; matches error messages ." ,
43+ Description : "Optional resource-level retry. When set, overrides provider HTTP retry for this resource ." ,
2844 Elem : & schema.Resource {
2945 Schema : map [string ]* schema.Schema {
30- "error_message_regex " : {
46+ "retry_on_messages " : {
3147 Type : schema .TypeList ,
3248 Optional : true ,
33- Description : "Regexes matched against error messages. If any match, the operation is retried ." ,
49+ Description : "Error message texts that trigger a retry. Defaults to provider transient/rate-limit messages when omitted ." ,
3450 Elem : & schema.Schema {Type : schema .TypeString },
3551 },
3652 "interval_seconds" : {
@@ -157,26 +173,27 @@ func resourceRetryConfigFromData(d *schema.ResourceData) (*resourceRetryConfig,
157173 cfg .Multiplier = v
158174 }
159175
160- patterns , _ := m ["error_message_regex " ].([]interface {})
176+ patterns , _ := m ["retry_on_messages " ].([]interface {})
161177 for _ , p := range patterns {
162178 s , ok := p .(string )
163179 if ! ok || s == "" {
164180 continue
165181 }
166182 re , err := regexp .Compile (s )
167183 if err != nil {
168- return nil , fmt .Errorf ("invalid retry.error_message_regex %q: %w" , s , err )
184+ return nil , fmt .Errorf ("invalid retry.retry_on_messages %q: %w" , s , err )
169185 }
170186 cfg .ErrorMessageRegex = append (cfg .ErrorMessageRegex , re )
171187 }
172188 if len (cfg .ErrorMessageRegex ) == 0 {
173- // Without message matchers, resource-level retry would retry every error — refuse that.
174- return nil , fmt .Errorf ("retry.error_message_regex must contain at least one pattern" )
189+ for _ , s := range defaultResourceRetryErrorPatterns {
190+ cfg .ErrorMessageRegex = append (cfg .ErrorMessageRegex , regexp .MustCompile (s ))
191+ }
175192 }
176193 return cfg , nil
177194}
178195
179- // RetryResourceOp runs fn, retrying when a resource retry block matches the error message .
196+ // RetryResourceOp wraps older CRUD hooks that return error (Create/Update/Delete) .
180197func RetryResourceOp (d * schema.ResourceData , fn func () error ) error {
181198 cfg , err := resourceRetryConfigFromData (d )
182199 if err != nil {
@@ -185,24 +202,11 @@ func RetryResourceOp(d *schema.ResourceData, fn func() error) error {
185202 if cfg == nil {
186203 return fn ()
187204 }
188-
189- var lastErr error
190- for attempt := 0 ; attempt <= cfg .MaxRetries ; attempt ++ {
191- lastErr = fn ()
192- if lastErr == nil {
193- return nil
194- }
195- if attempt == cfg .MaxRetries || ! cfg .matches (lastErr .Error ()) {
196- return lastErr
197- }
198- wait := cfg .backoff (attempt )
199- log .Printf ("[DEBUG] akeyless: resource retry attempt %d/%d after %s: %v" , attempt + 1 , cfg .MaxRetries , wait , lastErr )
200- time .Sleep (wait )
201- }
202- return lastErr
205+ return cfg .run (fn )
203206}
204207
205- // RetryResourceOpDiag is the Context-CRUD equivalent of RetryResourceOp.
208+ // RetryResourceOpDiag wraps Context CRUD hooks that return diag.Diagnostics
209+ // (CreateContext/UpdateContext/DeleteContext). Same retry policy as RetryResourceOp.
206210func RetryResourceOpDiag (d * schema.ResourceData , fn func () diag.Diagnostics ) diag.Diagnostics {
207211 cfg , err := resourceRetryConfigFromData (d )
208212 if err != nil {
@@ -213,23 +217,45 @@ func RetryResourceOpDiag(d *schema.ResourceData, fn func() diag.Diagnostics) dia
213217 }
214218
215219 var last diag.Diagnostics
216- for attempt := 0 ; attempt <= cfg .MaxRetries ; attempt ++ {
220+ _ = cfg .run ( func () error {
217221 last = fn ()
218222 if ! last .HasError () {
219- return last
223+ return nil
220224 }
221- msg := diagnosticsMessage (last )
222- if attempt == cfg .MaxRetries || ! cfg .matches (msg ) {
223- return last
225+ return fmt .Errorf ("%s" , diagnosticsMessage (last ))
226+ })
227+ return last
228+ }
229+
230+ // run is the shared resource-retry loop. Set skip inside RetryFunc: SDK runs it on a
231+ // worker goroutine (same one as HTTP calls).
232+ func (cfg * resourceRetryConfig ) run (fn func () error ) error {
233+ attempt := 0
234+ var lastErr error
235+ err := retry .RetryContext (context .Background (), sdkRetryTimeout , func () * retry.RetryError {
236+ SetSkipProviderHTTPRetry (true )
237+ defer SetSkipProviderHTTPRetry (false )
238+
239+ lastErr = fn ()
240+ if lastErr == nil {
241+ return nil
242+ }
243+ if attempt >= cfg .MaxRetries || ! cfg .matches (lastErr .Error ()) {
244+ return retry .NonRetryableError (lastErr )
224245 }
225246 wait := cfg .backoff (attempt )
226- log .Printf ("[DEBUG] akeyless: resource retry attempt %d/%d after %s: %s " , attempt + 1 , cfg .MaxRetries , wait , msg )
247+ log .Printf ("[DEBUG] akeyless: resource retry attempt %d/%d after %s: %v " , attempt + 1 , cfg .MaxRetries , wait , lastErr )
227248 time .Sleep (wait )
249+ attempt ++
250+ return retry .RetryableError (lastErr )
251+ })
252+ if lastErr != nil {
253+ return lastErr
228254 }
229- return last
255+ return err
230256}
231257
232- // diagnosticsMessage returns the first error text so resource retry can match error_message_regex .
258+ // diagnosticsMessage returns the first error text so resource retry can match retry_on_messages .
233259func diagnosticsMessage (diags diag.Diagnostics ) string {
234260 for _ , d := range diags {
235261 if d .Severity != diag .Error {
0 commit comments