Jelajahi Sumber

Merge pull request #18839 from aaronlehmann/v1-fallback-404

When a manifest is not found, allow fallback to v1
David Calavera 9 tahun lalu
induk
melakukan
95b708cf19

+ 18 - 0
distribution/pull_v2.go

@@ -13,6 +13,7 @@ import (
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
 	"github.com/docker/distribution/digest"
 	"github.com/docker/distribution/digest"
 	"github.com/docker/distribution/manifest/schema1"
 	"github.com/docker/distribution/manifest/schema1"
+	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/distribution/xfer"
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/image"
@@ -209,6 +210,23 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
 
 
 	unverifiedManifest, err := manSvc.GetByTag(tagOrDigest)
 	unverifiedManifest, err := manSvc.GetByTag(tagOrDigest)
 	if err != nil {
 	if err != nil {
+		// If this manifest did not exist, we should allow a possible
+		// fallback to the v1 protocol, because dual-version setups may
+		// not host all manifests with the v2 protocol. We may also get
+		// a "not authorized" error if the manifest doesn't exist.
+		switch v := err.(type) {
+		case errcode.Errors:
+			if len(v) != 0 {
+				if v0, ok := v[0].(errcode.Error); ok && registry.ShouldV2Fallback(v0) {
+					p.confirmedV2 = false
+				}
+			}
+		case errcode.Error:
+			if registry.ShouldV2Fallback(v) {
+				p.confirmedV2 = false
+			}
+		}
+
 		return false, err
 		return false, err
 	}
 	}
 	if unverifiedManifest == nil {
 	if unverifiedManifest == nil {

+ 12 - 0
integration-cli/docker_cli_pull_local_test.go

@@ -228,3 +228,15 @@ func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
 		c.Fatalf("expected %s; got %s", derivedImage, out)
 		c.Fatalf("expected %s; got %s", derivedImage, out)
 	}
 	}
 }
 }
+
+// TestPullFallbackOn404 tries to pull a nonexistent manifest and confirms that
+// the pull falls back to the v1 protocol.
+//
+// Ref: docker/docker#18832
+func (s *DockerRegistrySuite) TestPullFallbackOn404(c *check.C) {
+	repoName := fmt.Sprintf("%v/does/not/exist", privateRegistryURL)
+
+	out, _, _ := dockerCmdWithError("pull", repoName)
+
+	c.Assert(out, checker.Contains, "v1 ping attempt")
+}

+ 5 - 2
integration-cli/docker_cli_pull_test.go

@@ -1,6 +1,7 @@
 package main
 package main
 
 
 import (
 import (
+	"fmt"
 	"regexp"
 	"regexp"
 	"strings"
 	"strings"
 	"time"
 	"time"
@@ -53,8 +54,10 @@ func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
 	} {
 	} {
 		out, err := s.CmdWithError("pull", e.Alias)
 		out, err := s.CmdWithError("pull", e.Alias)
 		c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
 		c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
-		// Hub returns 401 rather than 404 for nonexistent library/ repos.
-		c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized", check.Commentf("expected unauthorized error message"))
+		// Hub returns 401 rather than 404 for nonexistent repos over
+		// the v2 protocol - but we should end up falling back to v1,
+		// which does return a 404.
+		c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
 	}
 	}
 }
 }
 
 

+ 3 - 3
registry/registry.go

@@ -188,8 +188,8 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
 	return nil
 	return nil
 }
 }
 
 
-func shouldV2Fallback(err errcode.Error) bool {
-	logrus.Debugf("v2 error: %T %v", err, err)
+// ShouldV2Fallback returns true if this error is a reason to fall back to v1.
+func ShouldV2Fallback(err errcode.Error) bool {
 	switch err.Code {
 	switch err.Code {
 	case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
 	case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
 		return true
 		return true
@@ -220,7 +220,7 @@ func ContinueOnError(err error) bool {
 	case ErrNoSupport:
 	case ErrNoSupport:
 		return ContinueOnError(v.Err)
 		return ContinueOnError(v.Err)
 	case errcode.Error:
 	case errcode.Error:
-		return shouldV2Fallback(v)
+		return ShouldV2Fallback(v)
 	case *client.UnexpectedHTTPResponseError:
 	case *client.UnexpectedHTTPResponseError:
 		return true
 		return true
 	case error:
 	case error: