Browse code

Do not fall back to the V1 protocol when we know we are talking to a V2 registry

If we detect a Docker-Distribution-Api-Version header indicating that
the registry speaks the V2 protocol, no fallback to V1 should take
place.

The same applies if a V2 registry operation succeeds while attempting a
push or pull.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2015/12/05 06:42:33
Showing 11 changed files
... ...
@@ -49,7 +49,7 @@ type Puller interface {
49 49
 	// Pull tries to pull the image referenced by `tag`
50 50
 	// Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
51 51
 	//
52
-	Pull(ctx context.Context, ref reference.Named) (fallback bool, err error)
52
+	Pull(ctx context.Context, ref reference.Named) error
53 53
 }
54 54
 
55 55
 // newPuller returns a Puller interface that will pull from either a v1 or v2
... ...
@@ -108,8 +108,17 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
108 108
 		// returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
109 109
 		// error is the ones from v2 endpoints not v1.
110 110
 		discardNoSupportErrors bool
111
+
112
+		// confirmedV2 is set to true if a pull attempt managed to
113
+		// confirm that it was talking to a v2 registry. This will
114
+		// prevent fallback to the v1 protocol.
115
+		confirmedV2 bool
111 116
 	)
112 117
 	for _, endpoint := range endpoints {
118
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
119
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
120
+			continue
121
+		}
113 122
 		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
114 123
 
115 124
 		puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
... ...
@@ -117,13 +126,18 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
117 117
 			errors = append(errors, err.Error())
118 118
 			continue
119 119
 		}
120
-		if fallback, err := puller.Pull(ctx, ref); err != nil {
120
+		if err := puller.Pull(ctx, ref); err != nil {
121 121
 			// Was this pull cancelled? If so, don't try to fall
122 122
 			// back.
123
+			fallback := false
123 124
 			select {
124 125
 			case <-ctx.Done():
125
-				fallback = false
126 126
 			default:
127
+				if fallbackErr, ok := err.(fallbackError); ok {
128
+					fallback = true
129
+					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
130
+					err = fallbackErr.err
131
+				}
127 132
 			}
128 133
 			if fallback {
129 134
 				if _, ok := err.(registry.ErrNoSupport); !ok {
... ...
@@ -33,15 +33,15 @@ type v1Puller struct {
33 33
 	session     *registry.Session
34 34
 }
35 35
 
36
-func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool, err error) {
36
+func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) error {
37 37
 	if _, isCanonical := ref.(reference.Canonical); isCanonical {
38 38
 		// Allowing fallback, because HTTPS v1 is before HTTP v2
39
-		return true, registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}
39
+		return fallbackError{err: registry.ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}}
40 40
 	}
41 41
 
42 42
 	tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
43 43
 	if err != nil {
44
-		return false, err
44
+		return err
45 45
 	}
46 46
 	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
47 47
 	tr := transport.NewTransport(
... ...
@@ -53,21 +53,21 @@ func (p *v1Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool
53 53
 	v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
54 54
 	if err != nil {
55 55
 		logrus.Debugf("Could not get v1 endpoint: %v", err)
56
-		return true, err
56
+		return fallbackError{err: err}
57 57
 	}
58 58
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
59 59
 	if err != nil {
60 60
 		// TODO(dmcgowan): Check if should fallback
61 61
 		logrus.Debugf("Fallback from error: %s", err)
62
-		return true, err
62
+		return fallbackError{err: err}
63 63
 	}
64 64
 	if err := p.pullRepository(ctx, ref); err != nil {
65 65
 		// TODO(dmcgowan): Check if should fallback
66
-		return false, err
66
+		return err
67 67
 	}
68 68
 	progress.Message(p.config.ProgressOutput, "", p.repoInfo.FullName()+": this image was pulled from a legacy registry.  Important: This registry version will not be supported in future versions of docker.")
69 69
 
70
-	return false, nil
70
+	return nil
71 71
 }
72 72
 
73 73
 func (p *v1Puller) pullRepository(ctx context.Context, ref reference.Named) error {
... ...
@@ -32,24 +32,26 @@ type v2Puller struct {
32 32
 	config         *ImagePullConfig
33 33
 	repoInfo       *registry.RepositoryInfo
34 34
 	repo           distribution.Repository
35
+	// confirmedV2 is set to true if we confirm we're talking to a v2
36
+	// registry. This is used to limit fallbacks to the v1 protocol.
37
+	confirmedV2 bool
35 38
 }
36 39
 
37
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (fallback bool, err error) {
40
+func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
38 41
 	// TODO(tiborvass): was ReceiveTimeout
39
-	p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
42
+	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
40 43
 	if err != nil {
41 44
 		logrus.Warnf("Error getting v2 registry: %v", err)
42
-		return true, err
45
+		return fallbackError{err: err, confirmedV2: p.confirmedV2}
43 46
 	}
44 47
 
45
-	if err := p.pullV2Repository(ctx, ref); err != nil {
48
+	if err = p.pullV2Repository(ctx, ref); err != nil {
46 49
 		if registry.ContinueOnError(err) {
47 50
 			logrus.Debugf("Error trying v2 registry: %v", err)
48
-			return true, err
51
+			return fallbackError{err: err, confirmedV2: p.confirmedV2}
49 52
 		}
50
-		return false, err
51 53
 	}
52
-	return false, nil
54
+	return err
53 55
 }
54 56
 
55 57
 func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
... ...
@@ -67,6 +69,10 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
67 67
 			return err
68 68
 		}
69 69
 
70
+		// If this call succeeded, we can be confident that the
71
+		// registry on the other side speaks the v2 protocol.
72
+		p.confirmedV2 = true
73
+
70 74
 		// This probably becomes a lot nicer after the manifest
71 75
 		// refactor...
72 76
 		for _, tag := range tags {
... ...
@@ -208,6 +214,11 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
208 208
 	if unverifiedManifest == nil {
209 209
 		return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
210 210
 	}
211
+
212
+	// If GetByTag succeeded, we can be confident that the registry on
213
+	// the other side speaks the v2 protocol.
214
+	p.confirmedV2 = true
215
+
211 216
 	var verifiedManifest *schema1.Manifest
212 217
 	verifiedManifest, err = verifyManifest(unverifiedManifest, ref)
213 218
 	if err != nil {
... ...
@@ -59,7 +59,7 @@ type Pusher interface {
59 59
 	// Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint.
60 60
 	//
61 61
 	// TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic.
62
-	Push(ctx context.Context) (fallback bool, err error)
62
+	Push(ctx context.Context) error
63 63
 }
64 64
 
65 65
 const compressionBufSize = 32768
... ...
@@ -116,8 +116,21 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
116 116
 		return fmt.Errorf("Repository does not exist: %s", repoInfo.Name())
117 117
 	}
118 118
 
119
-	var lastErr error
119
+	var (
120
+		lastErr error
121
+
122
+		// confirmedV2 is set to true if a push attempt managed to
123
+		// confirm that it was talking to a v2 registry. This will
124
+		// prevent fallback to the v1 protocol.
125
+		confirmedV2 bool
126
+	)
127
+
120 128
 	for _, endpoint := range endpoints {
129
+		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
130
+			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
131
+			continue
132
+		}
133
+
121 134
 		logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
122 135
 
123 136
 		pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
... ...
@@ -125,22 +138,22 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
125 125
 			lastErr = err
126 126
 			continue
127 127
 		}
128
-		if fallback, err := pusher.Push(ctx); err != nil {
128
+		if err := pusher.Push(ctx); err != nil {
129 129
 			// Was this push cancelled? If so, don't try to fall
130 130
 			// back.
131 131
 			select {
132 132
 			case <-ctx.Done():
133
-				fallback = false
134 133
 			default:
134
+				if fallbackErr, ok := err.(fallbackError); ok {
135
+					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
136
+					err = fallbackErr.err
137
+					lastErr = err
138
+					continue
139
+				}
135 140
 			}
136 141
 
137
-			if fallback {
138
-				lastErr = err
139
-				continue
140
-			}
141 142
 			logrus.Debugf("Not continuing with error: %v", err)
142 143
 			return err
143
-
144 144
 		}
145 145
 
146 146
 		imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
... ...
@@ -29,10 +29,10 @@ type v1Pusher struct {
29 29
 	session     *registry.Session
30 30
 }
31 31
 
32
-func (p *v1Pusher) Push(ctx context.Context) (fallback bool, err error) {
32
+func (p *v1Pusher) Push(ctx context.Context) error {
33 33
 	tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
34 34
 	if err != nil {
35
-		return false, err
35
+		return err
36 36
 	}
37 37
 	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
38 38
 	tr := transport.NewTransport(
... ...
@@ -44,18 +44,18 @@ func (p *v1Pusher) Push(ctx context.Context) (fallback bool, err error) {
44 44
 	v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
45 45
 	if err != nil {
46 46
 		logrus.Debugf("Could not get v1 endpoint: %v", err)
47
-		return true, err
47
+		return fallbackError{err: err}
48 48
 	}
49 49
 	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
50 50
 	if err != nil {
51 51
 		// TODO(dmcgowan): Check if should fallback
52
-		return true, err
52
+		return fallbackError{err: err}
53 53
 	}
54 54
 	if err := p.pushRepository(ctx); err != nil {
55 55
 		// TODO(dmcgowan): Check if should fallback
56
-		return false, err
56
+		return err
57 57
 	}
58
-	return false, nil
58
+	return nil
59 59
 }
60 60
 
61 61
 // v1Image exposes the configuration, filesystem layer ID, and a v1 ID for an
... ...
@@ -34,6 +34,10 @@ type v2Pusher struct {
34 34
 	config         *ImagePushConfig
35 35
 	repo           distribution.Repository
36 36
 
37
+	// confirmedV2 is set to true if we confirm we're talking to a v2
38
+	// registry. This is used to limit fallbacks to the v1 protocol.
39
+	confirmedV2 bool
40
+
37 41
 	// layersPushed is the set of layers known to exist on the remote side.
38 42
 	// This avoids redundant queries when pushing multiple tags that
39 43
 	// involve the same layers.
... ...
@@ -45,18 +49,27 @@ type pushMap struct {
45 45
 	layersPushed map[digest.Digest]bool
46 46
 }
47 47
 
48
-func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) {
49
-	p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
48
+func (p *v2Pusher) Push(ctx context.Context) (err error) {
49
+	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
50 50
 	if err != nil {
51 51
 		logrus.Debugf("Error getting v2 registry: %v", err)
52
-		return true, err
52
+		return fallbackError{err: err, confirmedV2: p.confirmedV2}
53 53
 	}
54 54
 
55
+	if err = p.pushV2Repository(ctx); err != nil {
56
+		if registry.ContinueOnError(err) {
57
+			return fallbackError{err: err, confirmedV2: p.confirmedV2}
58
+		}
59
+	}
60
+	return err
61
+}
62
+
63
+func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
55 64
 	var associations []reference.Association
56 65
 	if _, isTagged := p.ref.(reference.NamedTagged); isTagged {
57 66
 		imageID, err := p.config.ReferenceStore.Get(p.ref)
58 67
 		if err != nil {
59
-			return false, fmt.Errorf("tag does not exist: %s", p.ref.String())
68
+			return fmt.Errorf("tag does not exist: %s", p.ref.String())
60 69
 		}
61 70
 
62 71
 		associations = []reference.Association{
... ...
@@ -70,19 +83,19 @@ func (p *v2Pusher) Push(ctx context.Context) (fallback bool, err error) {
70 70
 		associations = p.config.ReferenceStore.ReferencesByName(p.ref)
71 71
 	}
72 72
 	if err != nil {
73
-		return false, fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err)
73
+		return fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err)
74 74
 	}
75 75
 	if len(associations) == 0 {
76
-		return false, fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
76
+		return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
77 77
 	}
78 78
 
79 79
 	for _, association := range associations {
80 80
 		if err := p.pushV2Tag(ctx, association); err != nil {
81
-			return false, err
81
+			return err
82 82
 		}
83 83
 	}
84 84
 
85
-	return false, nil
85
+	return nil
86 86
 }
87 87
 
88 88
 func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Association) error {
... ...
@@ -109,30 +122,28 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Associat
109 109
 
110 110
 	var descriptors []xfer.UploadDescriptor
111 111
 
112
+	descriptorTemplate := v2PushDescriptor{
113
+		blobSumService: p.blobSumService,
114
+		repo:           p.repo,
115
+		layersPushed:   &p.layersPushed,
116
+		confirmedV2:    &p.confirmedV2,
117
+	}
118
+
112 119
 	// Push empty layer if necessary
113 120
 	for _, h := range img.History {
114 121
 		if h.EmptyLayer {
115
-			descriptors = []xfer.UploadDescriptor{
116
-				&v2PushDescriptor{
117
-					layer:          layer.EmptyLayer,
118
-					blobSumService: p.blobSumService,
119
-					repo:           p.repo,
120
-					layersPushed:   &p.layersPushed,
121
-				},
122
-			}
122
+			descriptor := descriptorTemplate
123
+			descriptor.layer = layer.EmptyLayer
124
+			descriptors = []xfer.UploadDescriptor{&descriptor}
123 125
 			break
124 126
 		}
125 127
 	}
126 128
 
127 129
 	// Loop bounds condition is to avoid pushing the base layer on Windows.
128 130
 	for i := 0; i < len(img.RootFS.DiffIDs); i++ {
129
-		descriptor := &v2PushDescriptor{
130
-			layer:          l,
131
-			blobSumService: p.blobSumService,
132
-			repo:           p.repo,
133
-			layersPushed:   &p.layersPushed,
134
-		}
135
-		descriptors = append(descriptors, descriptor)
131
+		descriptor := descriptorTemplate
132
+		descriptor.layer = l
133
+		descriptors = append(descriptors, &descriptor)
136 134
 
137 135
 		l = l.Parent()
138 136
 	}
... ...
@@ -181,6 +192,7 @@ type v2PushDescriptor struct {
181 181
 	blobSumService *metadata.BlobSumService
182 182
 	repo           distribution.Repository
183 183
 	layersPushed   *pushMap
184
+	confirmedV2    *bool
184 185
 }
185 186
 
186 187
 func (pd *v2PushDescriptor) Key() string {
... ...
@@ -251,6 +263,10 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
251 251
 		return "", retryOnError(err)
252 252
 	}
253 253
 
254
+	// If Commit succeded, that's an indication that the remote registry
255
+	// speaks the v2 protocol.
256
+	*pd.confirmedV2 = true
257
+
254 258
 	logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
255 259
 	progress.Update(progressOutput, pd.ID(), "Pushed")
256 260
 
... ...
@@ -1,7 +1,6 @@
1 1
 package distribution
2 2
 
3 3
 import (
4
-	"errors"
5 4
 	"fmt"
6 5
 	"net"
7 6
 	"net/http"
... ...
@@ -24,6 +23,22 @@ import (
24 24
 	"golang.org/x/net/context"
25 25
 )
26 26
 
27
+// fallbackError wraps an error that can possibly allow fallback to a different
28
+// endpoint.
29
+type fallbackError struct {
30
+	// err is the error being wrapped.
31
+	err error
32
+	// confirmedV2 is set to true if it was confirmed that the registry
33
+	// supports the v2 protocol. This is used to limit fallbacks to the v1
34
+	// protocol.
35
+	confirmedV2 bool
36
+}
37
+
38
+// Error renders the FallbackError as a string.
39
+func (f fallbackError) Error() string {
40
+	return f.err.Error()
41
+}
42
+
27 43
 type dumbCredentialStore struct {
28 44
 	auth *types.AuthConfig
29 45
 }
... ...
@@ -35,9 +50,7 @@ func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) {
35 35
 // NewV2Repository returns a repository (v2 only). It creates a HTTP transport
36 36
 // providing timeout settings and authentication support, and also verifies the
37 37
 // remote API version.
38
-func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (distribution.Repository, error) {
39
-	ctx := context.Background()
40
-
38
+func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) {
41 39
 	repoName := repoInfo.FullName()
42 40
 	// If endpoint does not support CanonicalName, use the RemoteName instead
43 41
 	if endpoint.TrimHostname {
... ...
@@ -67,32 +80,34 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
67 67
 	endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
68 68
 	req, err := http.NewRequest("GET", endpointStr, nil)
69 69
 	if err != nil {
70
-		return nil, err
70
+		return nil, false, err
71 71
 	}
72 72
 	resp, err := pingClient.Do(req)
73 73
 	if err != nil {
74
-		return nil, err
74
+		return nil, false, err
75 75
 	}
76 76
 	defer resp.Body.Close()
77 77
 
78
-	versions := auth.APIVersions(resp, endpoint.VersionHeader)
79
-	if endpoint.VersionHeader != "" && len(endpoint.Versions) > 0 {
80
-		var foundVersion bool
81
-		for _, version := range endpoint.Versions {
82
-			for _, pingVersion := range versions {
83
-				if version == pingVersion {
84
-					foundVersion = true
85
-				}
86
-			}
87
-		}
88
-		if !foundVersion {
89
-			return nil, errors.New("endpoint does not support v2 API")
78
+	v2Version := auth.APIVersion{
79
+		Type:    "registry",
80
+		Version: "2.0",
81
+	}
82
+
83
+	versions := auth.APIVersions(resp, registry.DefaultRegistryVersionHeader)
84
+	for _, pingVersion := range versions {
85
+		if pingVersion == v2Version {
86
+			// The version header indicates we're definitely
87
+			// talking to a v2 registry. So don't allow future
88
+			// fallbacks to the v1 protocol.
89
+
90
+			foundVersion = true
91
+			break
90 92
 		}
91 93
 	}
92 94
 
93 95
 	challengeManager := auth.NewSimpleChallengeManager()
94 96
 	if err := challengeManager.AddResponse(resp); err != nil {
95
-		return nil, err
97
+		return nil, foundVersion, err
96 98
 	}
97 99
 
98 100
 	if authConfig.RegistryToken != "" {
... ...
@@ -106,7 +121,8 @@ func NewV2Repository(repoInfo *registry.RepositoryInfo, endpoint registry.APIEnd
106 106
 	}
107 107
 	tr := transport.NewTransport(base, modifiers...)
108 108
 
109
-	return client.NewRepository(ctx, repoName, endpoint.URL, tr)
109
+	repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr)
110
+	return repo, foundVersion, err
110 111
 }
111 112
 
112 113
 func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) {
... ...
@@ -147,7 +163,8 @@ func retryOnError(err error) error {
147 147
 		case errcode.ErrorCodeUnauthorized, errcode.ErrorCodeUnsupported, errcode.ErrorCodeDenied:
148 148
 			return xfer.DoNotRetry{Err: err}
149 149
 		}
150
-
150
+	case *client.UnexpectedHTTPResponseError:
151
+		return xfer.DoNotRetry{Err: err}
151 152
 	}
152 153
 	// let's be nice and fallback if the error is a completely
153 154
 	// unexpected one.
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/Sirupsen/logrus"
11
-	"github.com/docker/distribution/registry/client/auth"
12 11
 	"github.com/docker/docker/api/types"
13 12
 	registrytypes "github.com/docker/docker/api/types/registry"
14 13
 	"github.com/docker/docker/reference"
... ...
@@ -49,12 +48,6 @@ func TestTokenPassThru(t *testing.T) {
49 49
 		TrimHostname: false,
50 50
 		TLSConfig:    nil,
51 51
 		//VersionHeader: "verheader",
52
-		Versions: []auth.APIVersion{
53
-			{
54
-				Type:    "registry",
55
-				Version: "2",
56
-			},
57
-		},
58 52
 	}
59 53
 	n, _ := reference.ParseNamed("testremotename")
60 54
 	repoInfo := &registry.RepositoryInfo{
... ...
@@ -76,7 +69,8 @@ func TestTokenPassThru(t *testing.T) {
76 76
 		t.Fatal(err)
77 77
 	}
78 78
 	p := puller.(*v2Puller)
79
-	p.repo, err = NewV2Repository(p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
79
+	ctx := context.Background()
80
+	p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
80 81
 	if err != nil {
81 82
 		t.Fatal(err)
82 83
 	}
... ...
@@ -84,7 +78,7 @@ func TestTokenPassThru(t *testing.T) {
84 84
 	logrus.Debug("About to pull")
85 85
 	// We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
86 86
 	tag, _ := reference.WithTag(n, "tag_goes_here")
87
-	_ = p.pullV2Repository(context.Background(), tag)
87
+	_ = p.pullV2Repository(ctx, tag)
88 88
 
89 89
 	if !gotToken {
90 90
 		t.Fatal("Failed to receive registry token")
... ...
@@ -1,7 +1,6 @@
1 1
 package main
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"regexp"
6 5
 	"strings"
7 6
 	"time"
... ...
@@ -54,7 +53,8 @@ func (s *DockerHubPullSuite) TestPullNonExistingImage(c *check.C) {
54 54
 	} {
55 55
 		out, err := s.CmdWithError("pull", e.Alias)
56 56
 		c.Assert(err, checker.NotNil, check.Commentf("expected non-zero exit status when pulling non-existing image: %s", out))
57
-		c.Assert(out, checker.Contains, fmt.Sprintf("Error: image %s not found", e.Repo), check.Commentf("expected image not found error messages"))
57
+		// Hub returns 401 rather than 404 for nonexistent library/ repos.
58
+		c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized", check.Commentf("expected unauthorized error message"))
58 59
 	}
59 60
 }
60 61
 
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"net/url"
7 7
 	"strings"
8 8
 
9
-	"github.com/docker/distribution/registry/client/auth"
10 9
 	"github.com/docker/docker/api/types"
11 10
 	registrytypes "github.com/docker/docker/api/types/registry"
12 11
 	"github.com/docker/docker/reference"
... ...
@@ -121,14 +120,12 @@ func (s *Service) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
121 121
 
122 122
 // APIEndpoint represents a remote API endpoint
123 123
 type APIEndpoint struct {
124
-	Mirror        bool
125
-	URL           string
126
-	Version       APIVersion
127
-	Official      bool
128
-	TrimHostname  bool
129
-	TLSConfig     *tls.Config
130
-	VersionHeader string
131
-	Versions      []auth.APIVersion
124
+	Mirror       bool
125
+	URL          string
126
+	Version      APIVersion
127
+	Official     bool
128
+	TrimHostname bool
129
+	TLSConfig    *tls.Config
132 130
 }
133 131
 
134 132
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"strings"
6 6
 
7
-	"github.com/docker/distribution/registry/client/auth"
8 7
 	"github.com/docker/docker/pkg/tlsconfig"
9 8
 	"github.com/docker/docker/reference"
10 9
 )
... ...
@@ -52,20 +51,12 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
52 52
 		return nil, err
53 53
 	}
54 54
 
55
-	v2Versions := []auth.APIVersion{
56
-		{
57
-			Type:    "registry",
58
-			Version: "2.0",
59
-		},
60
-	}
61 55
 	endpoints = []APIEndpoint{
62 56
 		{
63
-			URL:           "https://" + hostname,
64
-			Version:       APIVersion2,
65
-			TrimHostname:  true,
66
-			TLSConfig:     tlsConfig,
67
-			VersionHeader: DefaultRegistryVersionHeader,
68
-			Versions:      v2Versions,
57
+			URL:          "https://" + hostname,
58
+			Version:      APIVersion2,
59
+			TrimHostname: true,
60
+			TLSConfig:    tlsConfig,
69 61
 		},
70 62
 	}
71 63
 
... ...
@@ -75,9 +66,7 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
75 75
 			Version:      APIVersion2,
76 76
 			TrimHostname: true,
77 77
 			// used to check if supposed to be secure via InsecureSkipVerify
78
-			TLSConfig:     tlsConfig,
79
-			VersionHeader: DefaultRegistryVersionHeader,
80
-			Versions:      v2Versions,
78
+			TLSConfig: tlsConfig,
81 79
 		})
82 80
 	}
83 81