diff --git a/util/resolver/authorizer.go b/util/resolver/authorizer.go index ced4b78fb4a1..834a5576cfe0 100644 --- a/util/resolver/authorizer.go +++ b/util/resolver/authorizer.go @@ -21,6 +21,7 @@ import ( "github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/errutil" "github.com/moby/buildkit/util/flightcontrol" + "github.com/moby/buildkit/util/resolver/retryhandler" "github.com/moby/buildkit/version" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -385,7 +386,9 @@ func (ah *authFetcher) fetchToken(ctx context.Context, sm *session.Manager, g se }() // try GET first because Docker Hub does not support POST // switch once support has landed - resp, err := auth.FetchToken(ctx, ah.client, nil, to) + resp, err := retryhandler.WithRetry(ctx, nil, func(ctx context.Context) (*auth.FetchTokenResponse, error) { + return auth.FetchToken(ctx, ah.client, nil, to) + }) if err != nil { var errStatus remoteserrors.ErrUnexpectedStatus if errors.As(err, &errStatus) { @@ -393,7 +396,9 @@ func (ah *authFetcher) fetchToken(ctx context.Context, sm *session.Manager, g se // As of September 2017, GCR is known to return 404. // As of February 2018, JFrog Artifactory is known to return 401. if (errStatus.StatusCode == http.StatusMethodNotAllowed && to.Username != "") || errStatus.StatusCode == http.StatusNotFound || errStatus.StatusCode == http.StatusUnauthorized { - resp, err := auth.FetchTokenWithOAuth(ctx, ah.client, hdr, "buildkit-client", to) + resp, err := retryhandler.WithRetry(ctx, nil, func(ctx context.Context) (*auth.OAuthTokenResponse, error) { + return auth.FetchTokenWithOAuth(ctx, ah.client, hdr, "buildkit-client", to) + }) if err != nil { return nil, err } @@ -419,7 +424,9 @@ func (ah *authFetcher) fetchToken(ctx context.Context, sm *session.Manager, g se return nil, nil } // do request anonymously - resp, err := auth.FetchToken(ctx, ah.client, hdr, to) + resp, err := retryhandler.WithRetry(ctx, nil, func(ctx context.Context) (*auth.FetchTokenResponse, error) { + return auth.FetchToken(ctx, ah.client, hdr, to) + }) if err != nil { return nil, errors.Wrap(err, "failed to fetch anonymous token") } diff --git a/util/resolver/retryhandler/retry.go b/util/resolver/retryhandler/retry.go index 63b9253ba02c..c8408bef5887 100644 --- a/util/resolver/retryhandler/retry.go +++ b/util/resolver/retryhandler/retry.go @@ -20,34 +20,42 @@ var MaxRetryBackoff = 8 * time.Second func New(f images.HandlerFunc, logger func([]byte)) images.HandlerFunc { return func(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) { - backoff := time.Second - for { - descs, err := f(ctx, desc) - if err != nil { - select { - case <-ctx.Done(): - return nil, err - default: - if !retryError(err) { - return nil, err - } - } - if logger != nil { - logger(fmt.Appendf(nil, "error: %v\n", err.Error())) + return WithRetry(ctx, logger, func(ctx context.Context) ([]ocispecs.Descriptor, error) { + return f(ctx, desc) + }) + } +} + +// WithRetry runs f, retrying on transient network errors with an +// exponential backoff, up to MaxRetryBackoff. +func WithRetry[T any](ctx context.Context, logger func([]byte), f func(ctx context.Context) (T, error)) (T, error) { + backoff := time.Second + for { + v, err := f(ctx) + if err != nil { + select { + case <-ctx.Done(): + return v, err + default: + if !retryError(err) { + return v, err } - } else { - return descs, nil - } - // backoff logic - if backoff >= MaxRetryBackoff { - return nil, err } if logger != nil { - logger(fmt.Appendf(nil, "retrying in %v\n", backoff)) + logger(fmt.Appendf(nil, "error: %v\n", err.Error())) } - time.Sleep(backoff) - backoff *= 2 + } else { + return v, nil + } + // backoff logic + if backoff >= MaxRetryBackoff { + return v, err + } + if logger != nil { + logger(fmt.Appendf(nil, "retrying in %v\n", backoff)) } + time.Sleep(backoff) + backoff *= 2 } }