Browse code

Merge pull request #18889 from aaronlehmann/v1-fallback-pull-all-tags

Allow v1 protocol fallback when pulling all tags from a repository unknown to v2 registry

Phil Estes authored on 2016/01/06 06:20:04
Showing 3 changed files
... ...
@@ -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