github.com/trgill/moby@v1.13.1/distribution/errors.go (about) 1 package distribution 2 3 import ( 4 "net/url" 5 "strings" 6 "syscall" 7 8 "github.com/Sirupsen/logrus" 9 "github.com/docker/distribution" 10 "github.com/docker/distribution/registry/api/errcode" 11 "github.com/docker/distribution/registry/api/v2" 12 "github.com/docker/distribution/registry/client" 13 "github.com/docker/distribution/registry/client/auth" 14 "github.com/docker/docker/distribution/xfer" 15 "github.com/docker/docker/reference" 16 "github.com/pkg/errors" 17 ) 18 19 // ErrNoSupport is an error type used for errors indicating that an operation 20 // is not supported. It encapsulates a more specific error. 21 type ErrNoSupport struct{ Err error } 22 23 func (e ErrNoSupport) Error() string { 24 if e.Err == nil { 25 return "not supported" 26 } 27 return e.Err.Error() 28 } 29 30 // fallbackError wraps an error that can possibly allow fallback to a different 31 // endpoint. 32 type fallbackError struct { 33 // err is the error being wrapped. 34 err error 35 // confirmedV2 is set to true if it was confirmed that the registry 36 // supports the v2 protocol. This is used to limit fallbacks to the v1 37 // protocol. 38 confirmedV2 bool 39 // transportOK is set to true if we managed to speak HTTP with the 40 // registry. This confirms that we're using appropriate TLS settings 41 // (or lack of TLS). 42 transportOK bool 43 } 44 45 // Error renders the FallbackError as a string. 46 func (f fallbackError) Error() string { 47 return f.Cause().Error() 48 } 49 50 func (f fallbackError) Cause() error { 51 return f.err 52 } 53 54 // shouldV2Fallback returns true if this error is a reason to fall back to v1. 55 func shouldV2Fallback(err errcode.Error) bool { 56 switch err.Code { 57 case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown: 58 return true 59 } 60 return false 61 } 62 63 // TranslatePullError is used to convert an error from a registry pull 64 // operation to an error representing the entire pull operation. Any error 65 // information which is not used by the returned error gets output to 66 // log at info level. 67 func TranslatePullError(err error, ref reference.Named) error { 68 switch v := err.(type) { 69 case errcode.Errors: 70 if len(v) != 0 { 71 for _, extra := range v[1:] { 72 logrus.Infof("Ignoring extra error returned from registry: %v", extra) 73 } 74 return TranslatePullError(v[0], ref) 75 } 76 case errcode.Error: 77 var newErr error 78 switch v.Code { 79 case errcode.ErrorCodeDenied: 80 // ErrorCodeDenied is used when access to the repository was denied 81 newErr = errors.Errorf("repository %s not found: does not exist or no pull access", ref.Name()) 82 case v2.ErrorCodeManifestUnknown: 83 newErr = errors.Errorf("manifest for %s not found", ref.String()) 84 case v2.ErrorCodeNameUnknown: 85 newErr = errors.Errorf("repository %s not found", ref.Name()) 86 } 87 if newErr != nil { 88 logrus.Infof("Translating %q to %q", err, newErr) 89 return newErr 90 } 91 case xfer.DoNotRetry: 92 return TranslatePullError(v.Err, ref) 93 } 94 95 return err 96 } 97 98 // continueOnError returns true if we should fallback to the next endpoint 99 // as a result of this error. 100 func continueOnError(err error) bool { 101 switch v := err.(type) { 102 case errcode.Errors: 103 if len(v) == 0 { 104 return true 105 } 106 return continueOnError(v[0]) 107 case ErrNoSupport: 108 return continueOnError(v.Err) 109 case errcode.Error: 110 return shouldV2Fallback(v) 111 case *client.UnexpectedHTTPResponseError: 112 return true 113 case ImageConfigPullError: 114 return false 115 case error: 116 return !strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) 117 } 118 // let's be nice and fallback if the error is a completely 119 // unexpected one. 120 // If new errors have to be handled in some way, please 121 // add them to the switch above. 122 return true 123 } 124 125 // retryOnError wraps the error in xfer.DoNotRetry if we should not retry the 126 // operation after this error. 127 func retryOnError(err error) error { 128 switch v := err.(type) { 129 case errcode.Errors: 130 if len(v) != 0 { 131 return retryOnError(v[0]) 132 } 133 case errcode.Error: 134 switch v.Code { 135 case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied, errcode.ErrorCodeTooManyRequests, v2.ErrorCodeNameUnknown: 136 return xfer.DoNotRetry{Err: err} 137 } 138 case *url.Error: 139 switch v.Err { 140 case auth.ErrNoBasicAuthCredentials, auth.ErrNoToken: 141 return xfer.DoNotRetry{Err: v.Err} 142 } 143 return retryOnError(v.Err) 144 case *client.UnexpectedHTTPResponseError: 145 return xfer.DoNotRetry{Err: err} 146 case error: 147 if err == distribution.ErrBlobUnknown { 148 return xfer.DoNotRetry{Err: err} 149 } 150 if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) { 151 return xfer.DoNotRetry{Err: err} 152 } 153 } 154 // let's be nice and fallback if the error is a completely 155 // unexpected one. 156 // If new errors have to be handled in some way, please 157 // add them to the switch above. 158 return err 159 }