mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #20247 from aaronlehmann/distribution-errors
Push/pull errors improvement and cleanup
This commit is contained in:
commit
e17afedfe0
8 changed files with 128 additions and 108 deletions
102
distribution/errors.go
Normal file
102
distribution/errors.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package distribution
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
|
"github.com/docker/distribution/registry/client"
|
||||||
|
"github.com/docker/docker/distribution/xfer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoSupport is an error type used for errors indicating that an operation
|
||||||
|
// is not supported. It encapsulates a more specific error.
|
||||||
|
type ErrNoSupport struct{ Err error }
|
||||||
|
|
||||||
|
func (e ErrNoSupport) Error() string {
|
||||||
|
if e.Err == nil {
|
||||||
|
return "not supported"
|
||||||
|
}
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallbackError wraps an error that can possibly allow fallback to a different
|
||||||
|
// endpoint.
|
||||||
|
type fallbackError struct {
|
||||||
|
// err is the error being wrapped.
|
||||||
|
err error
|
||||||
|
// confirmedV2 is set to true if it was confirmed that the registry
|
||||||
|
// supports the v2 protocol. This is used to limit fallbacks to the v1
|
||||||
|
// protocol.
|
||||||
|
confirmedV2 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error renders the FallbackError as a string.
|
||||||
|
func (f fallbackError) Error() string {
|
||||||
|
return f.err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldV2Fallback returns true if this error is a reason to fall back to v1.
|
||||||
|
func shouldV2Fallback(err errcode.Error) bool {
|
||||||
|
switch err.Code {
|
||||||
|
case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// continueOnError returns true if we should fallback to the next endpoint
|
||||||
|
// as a result of this error.
|
||||||
|
func continueOnError(err error) bool {
|
||||||
|
switch v := err.(type) {
|
||||||
|
case errcode.Errors:
|
||||||
|
if len(v) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return continueOnError(v[0])
|
||||||
|
case ErrNoSupport:
|
||||||
|
return continueOnError(v.Err)
|
||||||
|
case errcode.Error:
|
||||||
|
return shouldV2Fallback(v)
|
||||||
|
case *client.UnexpectedHTTPResponseError:
|
||||||
|
return true
|
||||||
|
case ImageConfigPullError:
|
||||||
|
return false
|
||||||
|
case error:
|
||||||
|
return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error()))
|
||||||
|
}
|
||||||
|
// let's be nice and fallback if the error is a completely
|
||||||
|
// unexpected one.
|
||||||
|
// If new errors have to be handled in some way, please
|
||||||
|
// add them to the switch above.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// retryOnError wraps the error in xfer.DoNotRetry if we should not retry the
|
||||||
|
// operation after this error.
|
||||||
|
func retryOnError(err error) error {
|
||||||
|
switch v := err.(type) {
|
||||||
|
case errcode.Errors:
|
||||||
|
return retryOnError(v[0])
|
||||||
|
case errcode.Error:
|
||||||
|
switch v.Code {
|
||||||
|
case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
|
||||||
|
return xfer.DoNotRetry{Err: err}
|
||||||
|
}
|
||||||
|
case *url.Error:
|
||||||
|
return retryOnError(v.Err)
|
||||||
|
case *client.UnexpectedHTTPResponseError:
|
||||||
|
return xfer.DoNotRetry{Err: err}
|
||||||
|
case error:
|
||||||
|
if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) {
|
||||||
|
return xfer.DoNotRetry{Err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// let's be nice and fallback if the error is a completely
|
||||||
|
// unexpected one.
|
||||||
|
// If new errors have to be handled in some way, please
|
||||||
|
// add them to the switch above.
|
||||||
|
return err
|
||||||
|
}
|
|
@ -136,7 +136,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fallback {
|
if fallback {
|
||||||
if _, ok := err.(registry.ErrNoSupport); !ok {
|
if _, ok := err.(ErrNoSupport); !ok {
|
||||||
// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
|
// Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
|
||||||
discardNoSupportErrors = true
|
discardNoSupportErrors = true
|
||||||
// append subsequent errors
|
// append subsequent errors
|
||||||
|
@ -147,9 +147,10 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
|
||||||
// append subsequent errors
|
// append subsequent errors
|
||||||
lastErr = err
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
logrus.Errorf("Attempting next endpoint for pull after error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
logrus.Debugf("Not continuing with error: %v", err)
|
logrus.Errorf("Not continuing with pull after error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ type v1Puller struct {
|
||||||
func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
|
func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
|
||||||
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||||
// Allowing fallback, because HTTPS v1 is before HTTP v2
|
// Allowing fallback, because HTTPS v1 is before HTTP v2
|
||||||
return fallbackError{err: registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
|
return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
|
||||||
|
|
|
@ -35,6 +35,17 @@ import (
|
||||||
|
|
||||||
var errRootFSMismatch = errors.New("layers from manifest don't match image configuration")
|
var errRootFSMismatch = errors.New("layers from manifest don't match image configuration")
|
||||||
|
|
||||||
|
// ImageConfigPullError is an error pulling the image config blob
|
||||||
|
// (only applies to schema2).
|
||||||
|
type ImageConfigPullError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error string for ImageConfigPullError.
|
||||||
|
func (e ImageConfigPullError) Error() string {
|
||||||
|
return "error pulling image configuration: " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
type v2Puller struct {
|
type v2Puller struct {
|
||||||
V2MetadataService *metadata.V2MetadataService
|
V2MetadataService *metadata.V2MetadataService
|
||||||
endpoint registry.APIEndpoint
|
endpoint registry.APIEndpoint
|
||||||
|
@ -58,8 +69,8 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
||||||
if _, ok := err.(fallbackError); ok {
|
if _, ok := err.(fallbackError); ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if registry.ContinueOnError(err) {
|
if continueOnError(err) {
|
||||||
logrus.Debugf("Error trying v2 registry: %v", err)
|
logrus.Errorf("Error trying v2 registry: %v", err)
|
||||||
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,7 +181,7 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
|
||||||
|
|
||||||
layerDownload, err := blobs.Open(ctx, ld.digest)
|
layerDownload, err := blobs.Open(ctx, ld.digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Error initiating layer download: %v", err)
|
logrus.Errorf("Error initiating layer download: %v", err)
|
||||||
if err == distribution.ErrBlobUnknown {
|
if err == distribution.ErrBlobUnknown {
|
||||||
return nil, 0, xfer.DoNotRetry{Err: err}
|
return nil, 0, xfer.DoNotRetry{Err: err}
|
||||||
}
|
}
|
||||||
|
@ -280,12 +291,12 @@ func (ld *v2LayerDescriptor) truncateDownloadFile() error {
|
||||||
ld.verifier = nil
|
ld.verifier = nil
|
||||||
|
|
||||||
if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil {
|
if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil {
|
||||||
logrus.Debugf("error seeking to beginning of download file: %v", err)
|
logrus.Errorf("error seeking to beginning of download file: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ld.tmpFile.Truncate(0); err != nil {
|
if err := ld.tmpFile.Truncate(0); err != nil {
|
||||||
logrus.Debugf("error truncating download file: %v", err)
|
logrus.Errorf("error truncating download file: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +495,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
||||||
go func() {
|
go func() {
|
||||||
configJSON, err := p.pullSchema2ImageConfig(ctx, target.Digest)
|
configJSON, err := p.pullSchema2ImageConfig(ctx, target.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- ImageConfigPullError{Err: err}
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -704,12 +715,12 @@ func allowV1Fallback(err error) error {
|
||||||
switch v := err.(type) {
|
switch v := err.(type) {
|
||||||
case errcode.Errors:
|
case errcode.Errors:
|
||||||
if len(v) != 0 {
|
if len(v) != 0 {
|
||||||
if v0, ok := v[0].(errcode.Error); ok && registry.ShouldV2Fallback(v0) {
|
if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) {
|
||||||
return fallbackError{err: err, confirmedV2: false}
|
return fallbackError{err: err, confirmedV2: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case errcode.Error:
|
case errcode.Error:
|
||||||
if registry.ShouldV2Fallback(v) {
|
if shouldV2Fallback(v) {
|
||||||
return fallbackError{err: err, confirmedV2: false}
|
return fallbackError{err: err, confirmedV2: false}
|
||||||
}
|
}
|
||||||
case *url.Error:
|
case *url.Error:
|
||||||
|
|
|
@ -144,11 +144,12 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
|
||||||
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
|
confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
|
||||||
err = fallbackErr.err
|
err = fallbackErr.err
|
||||||
lastErr = err
|
lastErr = err
|
||||||
|
logrus.Errorf("Attempting next endpoint for push after error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("Not continuing with error: %v", err)
|
logrus.Errorf("Not continuing with push after error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ func (p *v2Pusher) Push(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = p.pushV2Repository(ctx); err != nil {
|
if err = p.pushV2Repository(ctx); err != nil {
|
||||||
if registry.ContinueOnError(err) {
|
if continueOnError(err) {
|
||||||
return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
|
return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,38 +6,19 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/docker/distribution"
|
||||||
distreference "github.com/docker/distribution/reference"
|
distreference "github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/docker/distribution/registry/client"
|
"github.com/docker/distribution/registry/client"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/docker/distribution/xfer"
|
|
||||||
"github.com/docker/docker/dockerversion"
|
"github.com/docker/docker/dockerversion"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/engine-api/types"
|
"github.com/docker/engine-api/types"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fallbackError wraps an error that can possibly allow fallback to a different
|
|
||||||
// endpoint.
|
|
||||||
type fallbackError struct {
|
|
||||||
// err is the error being wrapped.
|
|
||||||
err error
|
|
||||||
// confirmedV2 is set to true if it was confirmed that the registry
|
|
||||||
// supports the v2 protocol. This is used to limit fallbacks to the v1
|
|
||||||
// protocol.
|
|
||||||
confirmedV2 bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error renders the FallbackError as a string.
|
|
||||||
func (f fallbackError) Error() string {
|
|
||||||
return f.err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type dumbCredentialStore struct {
|
type dumbCredentialStore struct {
|
||||||
auth *types.AuthConfig
|
auth *types.AuthConfig
|
||||||
}
|
}
|
||||||
|
@ -141,30 +122,3 @@ func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[s
|
||||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// retryOnError wraps the error in xfer.DoNotRetry if we should not retry the
|
|
||||||
// operation after this error.
|
|
||||||
func retryOnError(err error) error {
|
|
||||||
switch v := err.(type) {
|
|
||||||
case errcode.Errors:
|
|
||||||
return retryOnError(v[0])
|
|
||||||
case errcode.Error:
|
|
||||||
switch v.Code {
|
|
||||||
case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
|
|
||||||
return xfer.DoNotRetry{Err: err}
|
|
||||||
}
|
|
||||||
case *url.Error:
|
|
||||||
return retryOnError(v.Err)
|
|
||||||
case *client.UnexpectedHTTPResponseError:
|
|
||||||
return xfer.DoNotRetry{Err: err}
|
|
||||||
case error:
|
|
||||||
if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) {
|
|
||||||
return xfer.DoNotRetry{Err: err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// let's be nice and fallback if the error is a completely
|
|
||||||
// unexpected one.
|
|
||||||
// If new errors have to be handled in some way, please
|
|
||||||
// add them to the switch above.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,13 +13,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
|
||||||
"github.com/docker/distribution/registry/client"
|
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
@ -169,51 +165,6 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldV2Fallback returns true if this error is a reason to fall back to v1.
|
|
||||||
func ShouldV2Fallback(err errcode.Error) bool {
|
|
||||||
switch err.Code {
|
|
||||||
case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNoSupport is an error type used for errors indicating that an operation
|
|
||||||
// is not supported. It encapsulates a more specific error.
|
|
||||||
type ErrNoSupport struct{ Err error }
|
|
||||||
|
|
||||||
func (e ErrNoSupport) Error() string {
|
|
||||||
if e.Err == nil {
|
|
||||||
return "not supported"
|
|
||||||
}
|
|
||||||
return e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContinueOnError returns true if we should fallback to the next endpoint
|
|
||||||
// as a result of this error.
|
|
||||||
func ContinueOnError(err error) bool {
|
|
||||||
switch v := err.(type) {
|
|
||||||
case errcode.Errors:
|
|
||||||
if len(v) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return ContinueOnError(v[0])
|
|
||||||
case ErrNoSupport:
|
|
||||||
return ContinueOnError(v.Err)
|
|
||||||
case errcode.Error:
|
|
||||||
return ShouldV2Fallback(v)
|
|
||||||
case *client.UnexpectedHTTPResponseError:
|
|
||||||
return true
|
|
||||||
case error:
|
|
||||||
return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error()))
|
|
||||||
}
|
|
||||||
// let's be nice and fallback if the error is a completely
|
|
||||||
// unexpected one.
|
|
||||||
// If new errors have to be handled in some way, please
|
|
||||||
// add them to the switch above.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
|
// NewTransport returns a new HTTP transport. If tlsConfig is nil, it uses the
|
||||||
// default TLS configuration.
|
// default TLS configuration.
|
||||||
func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
func NewTransport(tlsConfig *tls.Config) *http.Transport {
|
||||||
|
|
Loading…
Add table
Reference in a new issue