Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 9d6acbe

Browse files
committed
When a manifest is not found, allow fallback to v1
PR #18590 caused compatibility issues with registries such as gcr.io which support both the v1 and v2 protocols, but do not provide the same set of images over both protocols. After #18590, pulls from these registries would never use the v1 protocol, because of the Docker-Distribution-Api-Version header indicating that v2 was supported. Fix the problem by making an exception for the case where a manifest is not found. This should allow fallback to v1 in case that image is exposed over the v1 protocol but not the v2 protocol. This avoids the overly aggressive fallback behavior before #18590 which would allow protocol fallback after almost any error, but restores interoperability with mixed v1/v2 registry setups. Fixes #18832 Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
1 parent 312c826 commit 9d6acbe

File tree

4 files changed

+38
-5
lines changed

4 files changed

+38
-5
lines changed

distribution/pull_v2.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/docker/distribution"
1414
"github.com/docker/distribution/digest"
1515
"github.com/docker/distribution/manifest/schema1"
16+
"github.com/docker/distribution/registry/api/errcode"
1617
"github.com/docker/docker/distribution/metadata"
1718
"github.com/docker/docker/distribution/xfer"
1819
"github.com/docker/docker/image"
@@ -209,6 +210,23 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
209210

210211
unverifiedManifest, err := manSvc.GetByTag(tagOrDigest)
211212
if err != nil {
213+
// If this manifest did not exist, we should allow a possible
214+
// fallback to the v1 protocol, because dual-version setups may
215+
// not host all manifests with the v2 protocol. We may also get
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+
212230
return false, err
213231
}
214232
if unverifiedManifest == nil {

integration-cli/docker_cli_pull_local_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,15 @@ func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
228228
c.Fatalf("expected %s; got %s", derivedImage, out)
229229
}
230230
}
231+
232+
// TestPullFallbackOn404 tries to pull a nonexistent manifest and confirms that
233+
// the pull falls back to the v1 protocol.
234+
//
235+
// Ref: docker/docker#18832
236+
func (s *DockerRegistrySuite) TestPullFallbackOn404(c *check.C) {
237+
repoName := fmt.Sprintf("%v/does/not/exist", privateRegistryURL)
238+
239+
out, _, _ := dockerCmdWithError("pull", repoName)
240+
241+
c.Assert(out, checker.Contains, "v1 ping attempt")
242+
}

integration-cli/docker_cli_pull_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

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

registry/registry.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
188188
return nil
189189
}
190190

191-
func shouldV2Fallback(err errcode.Error) bool {
192-
logrus.Debugf("v2 error: %T %v", err, err)
191+
// ShouldV2Fallback returns true if this error is a reason to fall back to v1.
192+
func ShouldV2Fallback(err errcode.Error) bool {
193193
switch err.Code {
194194
case errcode.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
195195
return true
@@ -220,7 +220,7 @@ func ContinueOnError(err error) bool {
220220
case ErrNoSupport:
221221
return ContinueOnError(v.Err)
222222
case errcode.Error:
223-
return shouldV2Fallback(v)
223+
return ShouldV2Fallback(v)
224224
case *client.UnexpectedHTTPResponseError:
225225
return true
226226
case error:

0 commit comments

Comments
 (0)