diff --git a/pkg/client/docker/docker.go b/pkg/client/docker/docker.go index 4662f3af..e3951bdd 100644 --- a/pkg/client/docker/docker.go +++ b/pkg/client/docker/docker.go @@ -12,8 +12,9 @@ import ( "github.com/sirupsen/logrus" - "github.com/hashicorp/go-retryablehttp" + retryablehttp "github.com/hashicorp/go-retryablehttp" "github.com/jetstack/version-checker/pkg/api" + "github.com/jetstack/version-checker/pkg/client/util" ) const ( @@ -41,8 +42,10 @@ func New(opts Options, log *logrus.Entry) (*Client, error) { } retryclient.HTTPClient.Timeout = 10 * time.Second retryclient.RetryMax = 10 - retryclient.RetryWaitMax = 2 * time.Minute + retryclient.RetryWaitMax = 10 * time.Minute retryclient.RetryWaitMin = 1 * time.Second + // This custom backoff will fail requests that have a max wait of the RetryWaitMax + retryclient.Backoff = util.HTTPBackOff retryclient.Logger = log.WithField("client", "docker") client := retryclient.StandardClient() diff --git a/pkg/client/util/http_backoff.go b/pkg/client/util/http_backoff.go new file mode 100644 index 00000000..465c5210 --- /dev/null +++ b/pkg/client/util/http_backoff.go @@ -0,0 +1,19 @@ +package util + +import ( + "net/http" + "time" + + "github.com/hashicorp/go-retryablehttp" +) + +// This is a custom Backoff that enforces the Max wait duration. +// If the sleep is greater we refuse to sleep at all +func HTTPBackOff(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { + sleep := retryablehttp.DefaultBackoff(min, max, attemptNum, resp) + if sleep.Abs() <= max { + return sleep.Abs() + } + + return max.Abs() +} diff --git a/pkg/client/util/http_backoff_test.go b/pkg/client/util/http_backoff_test.go new file mode 100644 index 00000000..74428b33 --- /dev/null +++ b/pkg/client/util/http_backoff_test.go @@ -0,0 +1,63 @@ +package util + +import ( + "net/http" + "testing" + "time" + + "github.com/hashicorp/go-retryablehttp" + "github.com/stretchr/testify/assert" +) + +func TestHTTPBackOff(t *testing.T) { + tests := []struct { + name string + min time.Duration + max time.Duration + attemptNum int + resp *http.Response + expSleep time.Duration + }{ + { + name: "sleep within max duration", + min: 100 * time.Millisecond, + max: 1 * time.Second, + attemptNum: 1, + resp: nil, + expSleep: retryablehttp.DefaultBackoff(100*time.Millisecond, 1*time.Second, 1, nil), + }, + { + name: "sleep exceeds max duration (too many requests)", + min: 100 * time.Millisecond, + max: 500 * time.Millisecond, + attemptNum: 10, + resp: &http.Response{ + StatusCode: http.StatusTooManyRequests, + Header: http.Header{"Retry-After": []string{"3600"}}}, + expSleep: 500 * time.Millisecond, + }, + { + name: "zero max duration", + min: 100 * time.Millisecond, + max: 0, + attemptNum: 1, + resp: nil, + expSleep: 0, + }, + { + name: "negative max duration", + min: 100 * time.Millisecond, + max: -1 * time.Second, + attemptNum: 1, + resp: nil, + expSleep: time.Second, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + gotSleep := HTTPBackOff(test.min, test.max, test.attemptNum, test.resp) + assert.Equal(t, test.expSleep, gotSleep, "unexpected sleep duration") + }) + } +}