561f02cc2f
* builder/azure-arm: Update logic for setting subscriptionID Previously, when using managed identities, the Azure builder would set the SubscriptionID in the Prepare method. But would not update it after getting the updated SubscriptionID from the metadata server. This change updates the Run method to ensure a valid subscriptionID is saved to the statebag before continuing with an image build. Co-authored-by: Paul Meyer <paul.meyer@outlook.com>
109 lines
2.8 KiB
Go
109 lines
2.8 KiB
Go
package retry
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
// Config represents a retry config
|
|
type Config struct {
|
|
// The operation will be retried until StartTimeout has elapsed. 0 means
|
|
// forever.
|
|
StartTimeout time.Duration
|
|
|
|
// RetryDelay gives the time elapsed after a failure and before we try
|
|
// again. Returns 2s by default.
|
|
RetryDelay func() time.Duration
|
|
|
|
// Max number of retries, 0 means infinite
|
|
Tries int
|
|
|
|
// ShouldRetry tells whether error should be retried. Nil defaults to always
|
|
// true.
|
|
ShouldRetry func(error) bool
|
|
}
|
|
|
|
type RetryExhaustedError struct {
|
|
Err error
|
|
}
|
|
|
|
func (err *RetryExhaustedError) Error() string {
|
|
if err == nil || err.Err == nil {
|
|
return "<nil>"
|
|
}
|
|
return fmt.Sprintf("retry count exhausted. Last err: %s", err.Err)
|
|
}
|
|
|
|
// Run fn until context is cancelled up until StartTimeout time has passed.
|
|
func (cfg Config) Run(ctx context.Context, fn func(context.Context) error) error {
|
|
retryDelay := func() time.Duration { return 2 * time.Second }
|
|
if cfg.RetryDelay != nil {
|
|
retryDelay = cfg.RetryDelay
|
|
}
|
|
shouldRetry := func(error) bool { return true }
|
|
if cfg.ShouldRetry != nil {
|
|
shouldRetry = cfg.ShouldRetry
|
|
}
|
|
var startTimeout <-chan time.Time // nil chans never unlock !
|
|
if cfg.StartTimeout != 0 {
|
|
startTimeout = time.After(cfg.StartTimeout)
|
|
}
|
|
|
|
var err error
|
|
for try := 0; ; try++ {
|
|
if cfg.Tries != 0 && try == cfg.Tries {
|
|
return &RetryExhaustedError{err}
|
|
}
|
|
if err = fn(ctx); err == nil {
|
|
return nil
|
|
}
|
|
if !shouldRetry(err) {
|
|
return err
|
|
}
|
|
|
|
log.Print(fmt.Errorf("Retryable error: %s", err))
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return err
|
|
case <-startTimeout:
|
|
return err
|
|
default:
|
|
time.Sleep(retryDelay())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Backoff is a self contained backoff time calculator. This struct should be
|
|
// passed around as a copy as it changes its own fields upon any Backoff call.
|
|
// Backoff is not thread safe. For now only a Linear backoff call is
|
|
// implemented and the Exponential call will be implemented when needed.
|
|
type Backoff struct {
|
|
// Initial time to wait. A Backoff call will change this value.
|
|
InitialBackoff time.Duration
|
|
// Maximum time returned.
|
|
MaxBackoff time.Duration
|
|
// For a Linear backoff, InitialBackoff will be multiplied by Multiplier
|
|
// after each call.
|
|
Multiplier float64
|
|
}
|
|
|
|
// Linear Backoff returns a linearly increasing Duration.
|
|
// n = n * Multiplier.
|
|
// the first value of n is InitialBackoff. n is maxed by MaxBackoff.
|
|
func (lb *Backoff) Linear() time.Duration {
|
|
wait := lb.InitialBackoff
|
|
lb.InitialBackoff = time.Duration(lb.Multiplier * float64(lb.InitialBackoff))
|
|
if lb.MaxBackoff != 0 && lb.InitialBackoff > lb.MaxBackoff {
|
|
lb.InitialBackoff = lb.MaxBackoff
|
|
}
|
|
return wait
|
|
}
|
|
|
|
// Exponential backoff panics: not implemented, yet.
|
|
func (lb *Backoff) Exponential() time.Duration {
|
|
panic("not implemented, yet")
|
|
}
|