Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions util/resolver/authorizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -385,15 +386,19 @@ 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) {
// retry with POST request
// 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
}
Expand All @@ -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")
}
Expand Down
54 changes: 31 additions & 23 deletions util/resolver/retryhandler/retry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down