Expose registry error translation for plugin distribution
Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
(cherry picked from commit a12b466183e03621bc9e1c1e4deab6db8ec93f0a)
Signed-off-by: Victor Vieux <vieux@docker.com>
| ... | ... |
@@ -60,21 +60,25 @@ func shouldV2Fallback(err errcode.Error) bool {
|
| 60 | 60 |
return false |
| 61 | 61 |
} |
| 62 | 62 |
|
| 63 |
-func translatePullError(err error, ref reference.Named) error {
|
|
| 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 {
|
|
| 64 | 68 |
switch v := err.(type) {
|
| 65 | 69 |
case errcode.Errors: |
| 66 | 70 |
if len(v) != 0 {
|
| 67 | 71 |
for _, extra := range v[1:] {
|
| 68 | 72 |
logrus.Infof("Ignoring extra error returned from registry: %v", extra)
|
| 69 | 73 |
} |
| 70 |
- return translatePullError(v[0], ref) |
|
| 74 |
+ return TranslatePullError(v[0], ref) |
|
| 71 | 75 |
} |
| 72 | 76 |
case errcode.Error: |
| 73 | 77 |
var newErr error |
| 74 | 78 |
switch v.Code {
|
| 75 | 79 |
case errcode.ErrorCodeDenied: |
| 76 | 80 |
// ErrorCodeDenied is used when access to the repository was denied |
| 77 |
- newErr = errors.Errorf("repository %s not found: does not exist or no read access", ref.Name())
|
|
| 81 |
+ newErr = errors.Errorf("repository %s not found: does not exist or no pull access", ref.Name())
|
|
| 78 | 82 |
case v2.ErrorCodeManifestUnknown: |
| 79 | 83 |
newErr = errors.Errorf("manifest for %s not found", ref.String())
|
| 80 | 84 |
case v2.ErrorCodeNameUnknown: |
| ... | ... |
@@ -85,7 +89,7 @@ func translatePullError(err error, ref reference.Named) error {
|
| 85 | 85 |
return newErr |
| 86 | 86 |
} |
| 87 | 87 |
case xfer.DoNotRetry: |
| 88 |
- return translatePullError(v.Err, ref) |
|
| 88 |
+ return TranslatePullError(v.Err, ref) |
|
| 89 | 89 |
} |
| 90 | 90 |
|
| 91 | 91 |
return err |
| ... | ... |
@@ -168,7 +168,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo |
| 168 | 168 |
continue |
| 169 | 169 |
} |
| 170 | 170 |
logrus.Errorf("Not continuing with pull after error: %v", err)
|
| 171 |
- return translatePullError(err, ref) |
|
| 171 |
+ return TranslatePullError(err, ref) |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 | 174 |
imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull") |
| ... | ... |
@@ -179,7 +179,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo |
| 179 | 179 |
lastErr = fmt.Errorf("no endpoints found for %s", ref.String())
|
| 180 | 180 |
} |
| 181 | 181 |
|
| 182 |
- return translatePullError(lastErr, ref) |
|
| 182 |
+ return TranslatePullError(lastErr, ref) |
|
| 183 | 183 |
} |
| 184 | 184 |
|
| 185 | 185 |
// writeStatus writes a status message to out. If layersDownloaded is true, the |
| ... | ... |
@@ -70,17 +70,22 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end |
| 70 | 70 |
passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
|
| 71 | 71 |
modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler)) |
| 72 | 72 |
} else {
|
| 73 |
+ scope := auth.RepositoryScope{
|
|
| 74 |
+ Repository: repoName, |
|
| 75 |
+ Actions: actions, |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ // Keep image repositories blank for scope compatibility |
|
| 79 |
+ if repoInfo.Class != "image" {
|
|
| 80 |
+ scope.Class = repoInfo.Class |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 73 | 83 |
creds := registry.NewStaticCredentialStore(authConfig) |
| 74 | 84 |
tokenHandlerOptions := auth.TokenHandlerOptions{
|
| 75 | 85 |
Transport: authTransport, |
| 76 | 86 |
Credentials: creds, |
| 77 |
- Scopes: []auth.Scope{
|
|
| 78 |
- auth.RepositoryScope{
|
|
| 79 |
- Repository: repoName, |
|
| 80 |
- Actions: actions, |
|
| 81 |
- }, |
|
| 82 |
- }, |
|
| 83 |
- ClientID: registry.AuthClientID, |
|
| 87 |
+ Scopes: []auth.Scope{scope},
|
|
| 88 |
+ ClientID: registry.AuthClientID, |
|
| 84 | 89 |
} |
| 85 | 90 |
tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions) |
| 86 | 91 |
basicHandler := auth.NewBasicHandler(creds) |
| ... | ... |
@@ -98,7 +98,7 @@ func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
|
| 98 | 98 |
for record := range recordChan {
|
| 99 | 99 |
if len(record.option) == 0 {
|
| 100 | 100 |
c.Assert(record.err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", record.out))
|
| 101 |
- c.Assert(record.out, checker.Contains, fmt.Sprintf("repository %s not found: does not exist or no read access", record.e.repo), check.Commentf("expected image not found error messages"))
|
|
| 101 |
+ c.Assert(record.out, checker.Contains, fmt.Sprintf("repository %s not found: does not exist or no pull access", record.e.repo), check.Commentf("expected image not found error messages"))
|
|
| 102 | 102 |
} else {
|
| 103 | 103 |
// pull -a on a nonexistent registry should fall back as well |
| 104 | 104 |
c.Assert(record.err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", record.out))
|
| ... | ... |
@@ -85,6 +85,7 @@ func Pull(ref reference.Named, rs registry.Service, metaheader http.Header, auth |
| 85 | 85 |
logrus.Debugf("pull.go: error in ResolveRepository: %v", err)
|
| 86 | 86 |
return nil, err |
| 87 | 87 |
} |
| 88 |
+ repoInfo.Class = "plugin" |
|
| 88 | 89 |
|
| 89 | 90 |
if err := dockerdist.ValidateRepoName(repoInfo.Name()); err != nil {
|
| 90 | 91 |
logrus.Debugf("pull.go: error in ValidateRepoName: %v", err)
|
| ... | ... |
@@ -138,9 +139,8 @@ func Pull(ref reference.Named, rs registry.Service, metaheader http.Header, auth |
| 138 | 138 |
} |
| 139 | 139 |
manifest, err := msv.Get(context.Background(), "", distribution.WithTag(tag)) |
| 140 | 140 |
if err != nil {
|
| 141 |
- // TODO: change 401 to 404 |
|
| 142 | 141 |
logrus.Debugf("pull.go: error in msv.Get(): %v", err)
|
| 143 |
- return nil, err |
|
| 142 |
+ return nil, dockerdist.TranslatePullError(err, repoInfo) |
|
| 144 | 143 |
} |
| 145 | 144 |
|
| 146 | 145 |
_, pl, err := manifest.Payload() |
| ... | ... |
@@ -280,7 +280,11 @@ func newRepositoryInfo(config *serviceConfig, name reference.Named) (*Repository |
| 280 | 280 |
return nil, err |
| 281 | 281 |
} |
| 282 | 282 |
official := !strings.ContainsRune(name.Name(), '/') |
| 283 |
- return &RepositoryInfo{name, index, official}, nil
|
|
| 283 |
+ return &RepositoryInfo{
|
|
| 284 |
+ Named: name, |
|
| 285 |
+ Index: index, |
|
| 286 |
+ Official: official, |
|
| 287 |
+ }, nil |
|
| 284 | 288 |
} |
| 285 | 289 |
|
| 286 | 290 |
// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but |
| ... | ... |
@@ -67,4 +67,7 @@ type RepositoryInfo struct {
|
| 67 | 67 |
// If the registry is official, and the normalized name does not |
| 68 | 68 |
// contain a '/' (e.g. "foo"), then it is considered an official repo. |
| 69 | 69 |
Official bool |
| 70 |
+ // Class represents the class of the repository, such as "plugin" |
|
| 71 |
+ // or "image". |
|
| 72 |
+ Class string |
|
| 70 | 73 |
} |