Allow v1 protocol fallback when pulling all tags from a repository unknown to v2 registry
| ... | ... |
@@ -47,6 +47,9 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 | 49 |
if err = p.pullV2Repository(ctx, ref); err != nil {
|
| 50 |
+ if _, ok := err.(fallbackError); ok {
|
|
| 51 |
+ return err |
|
| 52 |
+ } |
|
| 50 | 53 |
if registry.ContinueOnError(err) {
|
| 51 | 54 |
logrus.Debugf("Error trying v2 registry: %v", err)
|
| 52 | 55 |
return fallbackError{err: err, confirmedV2: p.confirmedV2}
|
| ... | ... |
@@ -56,9 +59,13 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 | 58 |
func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
|
| 59 |
- var refs []reference.Named |
|
| 59 |
+ var layersDownloaded bool |
|
| 60 | 60 |
if !reference.IsNameOnly(ref) {
|
| 61 |
- refs = []reference.Named{ref}
|
|
| 61 |
+ var err error |
|
| 62 |
+ layersDownloaded, err = p.pullV2Tag(ctx, ref) |
|
| 63 |
+ if err != nil {
|
|
| 64 |
+ return err |
|
| 65 |
+ } |
|
| 62 | 66 |
} else {
|
| 63 | 67 |
manSvc, err := p.repo.Manifests(ctx) |
| 64 | 68 |
if err != nil {
|
| ... | ... |
@@ -67,11 +74,14 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e |
| 67 | 67 |
|
| 68 | 68 |
tags, err := manSvc.Tags() |
| 69 | 69 |
if err != nil {
|
| 70 |
- return err |
|
| 70 |
+ // If this repository doesn't exist on V2, we should |
|
| 71 |
+ // permit a fallback to V1. |
|
| 72 |
+ return allowV1Fallback(err) |
|
| 71 | 73 |
} |
| 72 | 74 |
|
| 73 |
- // If this call succeeded, we can be confident that the |
|
| 74 |
- // registry on the other side speaks the v2 protocol. |
|
| 75 |
+ // The v2 registry knows about this repository, so we will not |
|
| 76 |
+ // allow fallback to the v1 protocol even if we encounter an |
|
| 77 |
+ // error later on. |
|
| 75 | 78 |
p.confirmedV2 = true |
| 76 | 79 |
|
| 77 | 80 |
// This probably becomes a lot nicer after the manifest |
| ... | ... |
@@ -81,19 +91,20 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e |
| 81 | 81 |
if err != nil {
|
| 82 | 82 |
return err |
| 83 | 83 |
} |
| 84 |
- refs = append(refs, tagRef) |
|
| 85 |
- } |
|
| 86 |
- } |
|
| 87 |
- |
|
| 88 |
- var layersDownloaded bool |
|
| 89 |
- for _, pullRef := range refs {
|
|
| 90 |
- // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged |
|
| 91 |
- // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? |
|
| 92 |
- pulledNew, err := p.pullV2Tag(ctx, pullRef) |
|
| 93 |
- if err != nil {
|
|
| 94 |
- return err |
|
| 84 |
+ pulledNew, err := p.pullV2Tag(ctx, tagRef) |
|
| 85 |
+ if err != nil {
|
|
| 86 |
+ // Since this is the pull-all-tags case, don't |
|
| 87 |
+ // allow an error pulling a particular tag to |
|
| 88 |
+ // make the whole pull fall back to v1. |
|
| 89 |
+ if fallbackErr, ok := err.(fallbackError); ok {
|
|
| 90 |
+ return fallbackErr.err |
|
| 91 |
+ } |
|
| 92 |
+ return err |
|
| 93 |
+ } |
|
| 94 |
+ // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged |
|
| 95 |
+ // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? |
|
| 96 |
+ layersDownloaded = layersDownloaded || pulledNew |
|
| 95 | 97 |
} |
| 96 |
- layersDownloaded = layersDownloaded || pulledNew |
|
| 97 | 98 |
} |
| 98 | 99 |
|
| 99 | 100 |
writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) |
| ... | ... |
@@ -214,20 +225,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat |
| 214 | 214 |
// fallback to the v1 protocol, because dual-version setups may |
| 215 | 215 |
// not host all manifests with the v2 protocol. We may also get |
| 216 | 216 |
// a "not authorized" error if the manifest doesn't exist. |
| 217 |
- switch v := err.(type) {
|
|
| 218 |
- case errcode.Errors: |
|
| 219 |
- if len(v) != 0 {
|
|
| 220 |
- if v0, ok := v[0].(errcode.Error); ok && registry.ShouldV2Fallback(v0) {
|
|
| 221 |
- p.confirmedV2 = false |
|
| 222 |
- } |
|
| 223 |
- } |
|
| 224 |
- case errcode.Error: |
|
| 225 |
- if registry.ShouldV2Fallback(v) {
|
|
| 226 |
- p.confirmedV2 = false |
|
| 227 |
- } |
|
| 228 |
- } |
|
| 229 |
- |
|
| 230 |
- return false, err |
|
| 217 |
+ return false, allowV1Fallback(err) |
|
| 231 | 218 |
} |
| 232 | 219 |
if unverifiedManifest == nil {
|
| 233 | 220 |
return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
|
| ... | ... |
@@ -334,6 +332,27 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat |
| 334 | 334 |
return true, nil |
| 335 | 335 |
} |
| 336 | 336 |
|
| 337 |
+// allowV1Fallback checks if the error is a possible reason to fallback to v1 |
|
| 338 |
+// (even if confirmedV2 has been set already), and if so, wraps the error in |
|
| 339 |
+// a fallbackError with confirmedV2 set to false. Otherwise, it returns the |
|
| 340 |
+// error unmodified. |
|
| 341 |
+func allowV1Fallback(err error) error {
|
|
| 342 |
+ switch v := err.(type) {
|
|
| 343 |
+ case errcode.Errors: |
|
| 344 |
+ if len(v) != 0 {
|
|
| 345 |
+ if v0, ok := v[0].(errcode.Error); ok && registry.ShouldV2Fallback(v0) {
|
|
| 346 |
+ return fallbackError{err: err, confirmedV2: false}
|
|
| 347 |
+ } |
|
| 348 |
+ } |
|
| 349 |
+ case errcode.Error: |
|
| 350 |
+ if registry.ShouldV2Fallback(v) {
|
|
| 351 |
+ return fallbackError{err: err, confirmedV2: false}
|
|
| 352 |
+ } |
|
| 353 |
+ } |
|
| 354 |
+ |
|
| 355 |
+ return err |
|
| 356 |
+} |
|
| 357 |
+ |
|
| 337 | 358 |
func verifyManifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) {
|
| 338 | 359 |
// If pull by digest, then verify the manifest digest. NOTE: It is |
| 339 | 360 |
// important to do this first, before any other content validation. If the |
| ... | ... |
@@ -56,7 +56,15 @@ func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
|
| 56 | 56 |
// the v2 protocol - but we should end up falling back to v1, |
| 57 | 57 |
// which does return a 404. |
| 58 | 58 |
c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
|
| 59 |
+ |
|
| 60 |
+ // pull -a on a nonexistent registry should fall back as well |
|
| 61 |
+ if !strings.ContainsRune(e.Alias, ':') {
|
|
| 62 |
+ out, err := s.CmdWithError("pull", "-a", e.Alias)
|
|
| 63 |
+ c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
|
|
| 64 |
+ c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
|
|
| 65 |
+ } |
|
| 59 | 66 |
} |
| 67 |
+ |
|
| 60 | 68 |
} |
| 61 | 69 |
|
| 62 | 70 |
// TestPullFromCentralRegistryImplicitRefParts pulls an image from the central registry and verifies |
| ... | ... |
@@ -191,7 +191,7 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque |
| 191 | 191 |
// ShouldV2Fallback returns true if this error is a reason to fall back to v1. |
| 192 | 192 |
func ShouldV2Fallback(err errcode.Error) bool {
|
| 193 | 193 |
switch err.Code {
|
| 194 |
- case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown: |
|
| 194 |
+ case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown, v2.ErrorCodeNameUnknown: |
|
| 195 | 195 |
return true |
| 196 | 196 |
} |
| 197 | 197 |
return false |