Browse Source

Merge pull request #13265 from tiborvass/refactor-v1-auth

registry: Refactor requestfactory to use http.RoundTrippers
Arnaud Porterie 10 years ago
parent
commit
a16b2ab274

+ 15 - 7
graph/pull.go

@@ -17,6 +17,7 @@ import (
 	"github.com/docker/docker/pkg/progressreader"
 	"github.com/docker/docker/pkg/progressreader"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/transport"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/utils"
 	"github.com/docker/docker/utils"
 )
 )
@@ -55,12 +56,19 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
 	defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
 	defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag))
 
 
 	logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
 	logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
-	endpoint, err := repoInfo.GetEndpoint()
+
+	endpoint, err := repoInfo.GetEndpoint(imagePullConfig.MetaHeaders)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
-	r, err := registry.NewSession(imagePullConfig.AuthConfig, registry.HTTPRequestFactory(imagePullConfig.MetaHeaders), endpoint, true)
+	// TODO(tiborvass): reuse client from endpoint?
+	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+	tr := transport.NewTransport(
+		registry.NewTransport(registry.ReceiveTimeout, endpoint.IsSecure),
+		registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
+	)
+	client := registry.HTTPClient(tr)
+	r, err := registry.NewSession(client, imagePullConfig.AuthConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -109,7 +117,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
 	}
 	}
 
 
 	logrus.Debugf("Retrieving the tag list")
 	logrus.Debugf("Retrieving the tag list")
-	tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens)
+	tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName)
 	if err != nil {
 	if err != nil {
 		logrus.Errorf("unable to get remote tags: %s", err)
 		logrus.Errorf("unable to get remote tags: %s", err)
 		return err
 		return err
@@ -240,7 +248,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
 }
 }
 
 
 func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) {
 func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) {
-	history, err := r.GetRemoteHistory(imgID, endpoint, token)
+	history, err := r.GetRemoteHistory(imgID, endpoint)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
 	}
 	}
@@ -269,7 +277,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
 			)
 			)
 			retries := 5
 			retries := 5
 			for j := 1; j <= retries; j++ {
 			for j := 1; j <= retries; j++ {
-				imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
+				imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint)
 				if err != nil && j == retries {
 				if err != nil && j == retries {
 					out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
 					out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
 					return layersDownloaded, err
 					return layersDownloaded, err
@@ -297,7 +305,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
 					status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
 					status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
 				}
 				}
 				out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
 				out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
-				layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
+				layer, err := r.GetRemoteImageLayer(img.ID, endpoint, int64(imgSize))
 				if uerr, ok := err.(*url.Error); ok {
 				if uerr, ok := err.(*url.Error); ok {
 					err = uerr.Err
 					err = uerr.Err
 				}
 				}

+ 15 - 8
graph/push.go

@@ -18,6 +18,7 @@ import (
 	"github.com/docker/docker/pkg/progressreader"
 	"github.com/docker/docker/pkg/progressreader"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
+	"github.com/docker/docker/pkg/transport"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/utils"
 	"github.com/docker/docker/utils"
@@ -141,7 +142,7 @@ func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Write
 	images chan imagePushData, imagesToPush chan string) {
 	images chan imagePushData, imagesToPush chan string) {
 	defer wg.Done()
 	defer wg.Done()
 	for image := range images {
 	for image := range images {
-		if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil {
+		if err := r.LookupRemoteImage(image.id, image.endpoint); err != nil {
 			logrus.Errorf("Error in LookupRemoteImage: %s", err)
 			logrus.Errorf("Error in LookupRemoteImage: %s", err)
 			imagesToPush <- image.id
 			imagesToPush <- image.id
 			continue
 			continue
@@ -199,7 +200,7 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam
 		}
 		}
 		for _, tag := range tags[id] {
 		for _, tag := range tags[id] {
 			out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
 			out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag))
-			if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil {
+			if err := r.PushRegistryTag(remoteName, id, tag, endpoint); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -258,7 +259,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
 	}
 	}
 
 
 	// Send the json
 	// Send the json
-	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
+	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
 		if err == registry.ErrAlreadyExists {
 		if err == registry.ErrAlreadyExists {
 			out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
 			out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
 			return "", nil
 			return "", nil
@@ -284,14 +285,14 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin
 			NewLines:  false,
 			NewLines:  false,
 			ID:        stringid.TruncateID(imgData.ID),
 			ID:        stringid.TruncateID(imgData.ID),
 			Action:    "Pushing",
 			Action:    "Pushing",
-		}), ep, token, jsonRaw)
+		}), ep, jsonRaw)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 	imgData.Checksum = checksum
 	imgData.Checksum = checksum
 	imgData.ChecksumPayload = checksumPayload
 	imgData.ChecksumPayload = checksumPayload
 	// Send the checksum
 	// Send the checksum
-	if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
+	if err := r.PushImageChecksumRegistry(imgData, ep); err != nil {
 		return "", err
 		return "", err
 	}
 	}
 
 
@@ -509,12 +510,18 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro
 	}
 	}
 	defer s.poolRemove("push", repoInfo.LocalName)
 	defer s.poolRemove("push", repoInfo.LocalName)
 
 
-	endpoint, err := repoInfo.GetEndpoint()
+	endpoint, err := repoInfo.GetEndpoint(imagePushConfig.MetaHeaders)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
-	r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false)
+	// TODO(tiborvass): reuse client from endpoint?
+	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+	tr := transport.NewTransport(
+		registry.NewTransport(registry.NoTimeout, endpoint.IsSecure),
+		registry.DockerHeaders(imagePushConfig.MetaHeaders)...,
+	)
+	client := registry.HTTPClient(tr)
+	r, err := registry.NewSession(client, imagePushConfig.AuthConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 0 - 2
pkg/requestdecorator/README.md

@@ -1,2 +0,0 @@
-This package provides helper functions for decorating a request with user agent
-versions, auth, meta headers.

+ 0 - 172
pkg/requestdecorator/requestdecorator.go

@@ -1,172 +0,0 @@
-// Package requestdecorator provides helper functions to decorate a request with
-// user agent versions, auth, meta headers.
-package requestdecorator
-
-import (
-	"errors"
-	"io"
-	"net/http"
-	"strings"
-
-	"github.com/Sirupsen/logrus"
-)
-
-var (
-	ErrNilRequest = errors.New("request cannot be nil")
-)
-
-// UAVersionInfo is used to model UserAgent versions.
-type UAVersionInfo struct {
-	Name    string
-	Version string
-}
-
-func NewUAVersionInfo(name, version string) UAVersionInfo {
-	return UAVersionInfo{
-		Name:    name,
-		Version: version,
-	}
-}
-
-func (vi *UAVersionInfo) isValid() bool {
-	const stopChars = " \t\r\n/"
-	name := vi.Name
-	vers := vi.Version
-	if len(name) == 0 || strings.ContainsAny(name, stopChars) {
-		return false
-	}
-	if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
-		return false
-	}
-	return true
-}
-
-// Convert versions to a string and append the string to the string base.
-//
-// Each UAVersionInfo will be converted to a string in the format of
-// "product/version", where the "product" is get from the name field, while
-// version is get from the version field. Several pieces of verson information
-// will be concatinated and separated by space.
-func appendVersions(base string, versions ...UAVersionInfo) string {
-	if len(versions) == 0 {
-		return base
-	}
-
-	verstrs := make([]string, 0, 1+len(versions))
-	if len(base) > 0 {
-		verstrs = append(verstrs, base)
-	}
-
-	for _, v := range versions {
-		if !v.isValid() {
-			continue
-		}
-		verstrs = append(verstrs, v.Name+"/"+v.Version)
-	}
-	return strings.Join(verstrs, " ")
-}
-
-// Decorator is used to change an instance of
-// http.Request. It could be used to add more header fields,
-// change body, etc.
-type Decorator interface {
-	// ChangeRequest() changes the request accordingly.
-	// The changed request will be returned or err will be non-nil
-	// if an error occur.
-	ChangeRequest(req *http.Request) (newReq *http.Request, err error)
-}
-
-// UserAgentDecorator appends the product/version to the user agent field
-// of a request.
-type UserAgentDecorator struct {
-	Versions []UAVersionInfo
-}
-
-func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
-	if req == nil {
-		return req, ErrNilRequest
-	}
-
-	userAgent := appendVersions(req.UserAgent(), h.Versions...)
-	if len(userAgent) > 0 {
-		req.Header.Set("User-Agent", userAgent)
-	}
-	return req, nil
-}
-
-type MetaHeadersDecorator struct {
-	Headers map[string][]string
-}
-
-func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
-	if h.Headers == nil {
-		return req, ErrNilRequest
-	}
-	for k, v := range h.Headers {
-		req.Header[k] = v
-	}
-	return req, nil
-}
-
-type AuthDecorator struct {
-	login    string
-	password string
-}
-
-func NewAuthDecorator(login, password string) Decorator {
-	return &AuthDecorator{
-		login:    login,
-		password: password,
-	}
-}
-
-func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) {
-	if req == nil {
-		return req, ErrNilRequest
-	}
-	req.SetBasicAuth(self.login, self.password)
-	return req, nil
-}
-
-// RequestFactory creates an HTTP request
-// and applies a list of decorators on the request.
-type RequestFactory struct {
-	decorators []Decorator
-}
-
-func NewRequestFactory(d ...Decorator) *RequestFactory {
-	return &RequestFactory{
-		decorators: d,
-	}
-}
-
-func (f *RequestFactory) AddDecorator(d ...Decorator) {
-	f.decorators = append(f.decorators, d...)
-}
-
-func (f *RequestFactory) GetDecorators() []Decorator {
-	return f.decorators
-}
-
-// NewRequest() creates a new *http.Request,
-// applies all decorators in the Factory on the request,
-// then applies decorators provided by d on the request.
-func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) {
-	req, err := http.NewRequest(method, urlStr, body)
-	if err != nil {
-		return nil, err
-	}
-
-	// By default, a nil factory should work.
-	if h == nil {
-		return req, nil
-	}
-	for _, dec := range h.decorators {
-		req, _ = dec.ChangeRequest(req)
-	}
-	for _, dec := range d {
-		req, _ = dec.ChangeRequest(req)
-	}
-	logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
-	return req, err
-}

+ 0 - 222
pkg/requestdecorator/requestdecorator_test.go

@@ -1,222 +0,0 @@
-package requestdecorator
-
-import (
-	"net/http"
-	"strings"
-	"testing"
-)
-
-func TestUAVersionInfo(t *testing.T) {
-	uavi := NewUAVersionInfo("foo", "bar")
-	if !uavi.isValid() {
-		t.Fatalf("UAVersionInfo should be valid")
-	}
-	uavi = NewUAVersionInfo("", "bar")
-	if uavi.isValid() {
-		t.Fatalf("Expected UAVersionInfo to be invalid")
-	}
-	uavi = NewUAVersionInfo("foo", "")
-	if uavi.isValid() {
-		t.Fatalf("Expected UAVersionInfo to be invalid")
-	}
-}
-
-func TestUserAgentDecorator(t *testing.T) {
-	httpVersion := make([]UAVersionInfo, 2)
-	httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
-	httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
-	uad := &UserAgentDecorator{
-		Versions: httpVersion,
-	}
-
-	req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	reqDecorated, err := uad.ChangeRequest(req)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if reqDecorated.Header.Get("User-Agent") != "testname/testversion name/version" {
-		t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
-	}
-}
-
-func TestUserAgentDecoratorErr(t *testing.T) {
-	httpVersion := make([]UAVersionInfo, 0)
-	uad := &UserAgentDecorator{
-		Versions: httpVersion,
-	}
-
-	var req *http.Request
-	_, err := uad.ChangeRequest(req)
-	if err == nil {
-		t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
-	}
-}
-
-func TestMetaHeadersDecorator(t *testing.T) {
-	var headers = map[string][]string{
-		"key1": {"value1"},
-		"key2": {"value2"},
-	}
-	mhd := &MetaHeadersDecorator{
-		Headers: headers,
-	}
-
-	req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	reqDecorated, err := mhd.ChangeRequest(req)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	v, ok := reqDecorated.Header["key1"]
-	if !ok {
-		t.Fatalf("Expected to have header key1")
-	}
-	if v[0] != "value1" {
-		t.Fatalf("Expected value for key1 isn't value1")
-	}
-
-	v, ok = reqDecorated.Header["key2"]
-	if !ok {
-		t.Fatalf("Expected to have header key2")
-	}
-	if v[0] != "value2" {
-		t.Fatalf("Expected value for key2 isn't value2")
-	}
-}
-
-func TestMetaHeadersDecoratorErr(t *testing.T) {
-	mhd := &MetaHeadersDecorator{}
-
-	var req *http.Request
-	_, err := mhd.ChangeRequest(req)
-	if err == nil {
-		t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
-	}
-}
-
-func TestAuthDecorator(t *testing.T) {
-	ad := NewAuthDecorator("test", "password")
-
-	req, err := http.NewRequest("GET", "/something", strings.NewReader("test"))
-	if err != nil {
-		t.Fatal(err)
-	}
-	reqDecorated, err := ad.ChangeRequest(req)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	username, password, ok := reqDecorated.BasicAuth()
-	if !ok {
-		t.Fatalf("Cannot retrieve basic auth info from request")
-	}
-	if username != "test" {
-		t.Fatalf("Expected username to be test, got %s", username)
-	}
-	if password != "password" {
-		t.Fatalf("Expected password to be password, got %s", password)
-	}
-}
-
-func TestAuthDecoratorErr(t *testing.T) {
-	ad := &AuthDecorator{}
-
-	var req *http.Request
-	_, err := ad.ChangeRequest(req)
-	if err == nil {
-		t.Fatalf("Expected to get ErrNilRequest instead no error was returned")
-	}
-}
-
-func TestRequestFactory(t *testing.T) {
-	ad := NewAuthDecorator("test", "password")
-	httpVersion := make([]UAVersionInfo, 2)
-	httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion"))
-	httpVersion = append(httpVersion, NewUAVersionInfo("name", "version"))
-	uad := &UserAgentDecorator{
-		Versions: httpVersion,
-	}
-
-	requestFactory := NewRequestFactory(ad, uad)
-
-	if l := len(requestFactory.GetDecorators()); l != 2 {
-		t.Fatalf("Expected to have two decorators, got %d", l)
-	}
-
-	req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	username, password, ok := req.BasicAuth()
-	if !ok {
-		t.Fatalf("Cannot retrieve basic auth info from request")
-	}
-	if username != "test" {
-		t.Fatalf("Expected username to be test, got %s", username)
-	}
-	if password != "password" {
-		t.Fatalf("Expected password to be password, got %s", password)
-	}
-	if req.Header.Get("User-Agent") != "testname/testversion name/version" {
-		t.Fatalf("Request should have User-Agent 'testname/testversion name/version'")
-	}
-}
-
-func TestRequestFactoryNewRequestWithDecorators(t *testing.T) {
-	ad := NewAuthDecorator("test", "password")
-
-	requestFactory := NewRequestFactory(ad)
-
-	if l := len(requestFactory.GetDecorators()); l != 1 {
-		t.Fatalf("Expected to have one decorators, got %d", l)
-	}
-
-	ad2 := NewAuthDecorator("test2", "password2")
-
-	req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"), ad2)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	username, password, ok := req.BasicAuth()
-	if !ok {
-		t.Fatalf("Cannot retrieve basic auth info from request")
-	}
-	if username != "test2" {
-		t.Fatalf("Expected username to be test, got %s", username)
-	}
-	if password != "password2" {
-		t.Fatalf("Expected password to be password, got %s", password)
-	}
-}
-
-func TestRequestFactoryAddDecorator(t *testing.T) {
-	requestFactory := NewRequestFactory()
-
-	if l := len(requestFactory.GetDecorators()); l != 0 {
-		t.Fatalf("Expected to have zero decorators, got %d", l)
-	}
-
-	ad := NewAuthDecorator("test", "password")
-	requestFactory.AddDecorator(ad)
-
-	if l := len(requestFactory.GetDecorators()); l != 1 {
-		t.Fatalf("Expected to have one decorators, got %d", l)
-	}
-}
-
-func TestRequestFactoryNil(t *testing.T) {
-	var requestFactory RequestFactory
-	_, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"))
-	if err != nil {
-		t.Fatalf("Expected not to get and error, got %s", err)
-	}
-}

+ 27 - 0
pkg/transport/LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2009 The oauth2 Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 148 - 0
pkg/transport/transport.go

@@ -0,0 +1,148 @@
+package transport
+
+import (
+	"io"
+	"net/http"
+	"sync"
+)
+
+type RequestModifier interface {
+	ModifyRequest(*http.Request) error
+}
+
+type headerModifier http.Header
+
+// NewHeaderRequestModifier returns a RequestModifier that merges the HTTP headers
+// passed as an argument, with the HTTP headers of a request.
+//
+// If the same key is present in both, the modifying header values for that key,
+// are appended to the values for that same key in the request header.
+func NewHeaderRequestModifier(header http.Header) RequestModifier {
+	return headerModifier(header)
+}
+
+func (h headerModifier) ModifyRequest(req *http.Request) error {
+	for k, s := range http.Header(h) {
+		req.Header[k] = append(req.Header[k], s...)
+	}
+
+	return nil
+}
+
+// NewTransport returns an http.RoundTripper that modifies requests according to
+// the RequestModifiers passed in the arguments, before sending the requests to
+// the base http.RoundTripper (which, if nil, defaults to http.DefaultTransport).
+func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
+	return &transport{
+		Modifiers: modifiers,
+		Base:      base,
+	}
+}
+
+// transport is an http.RoundTripper that makes HTTP requests after
+// copying and modifying the request
+type transport struct {
+	Modifiers []RequestModifier
+	Base      http.RoundTripper
+
+	mu     sync.Mutex                      // guards modReq
+	modReq map[*http.Request]*http.Request // original -> modified
+}
+
+func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
+	req2 := CloneRequest(req)
+	for _, modifier := range t.Modifiers {
+		if err := modifier.ModifyRequest(req2); err != nil {
+			return nil, err
+		}
+	}
+
+	t.setModReq(req, req2)
+	res, err := t.base().RoundTrip(req2)
+	if err != nil {
+		t.setModReq(req, nil)
+		return nil, err
+	}
+	res.Body = &OnEOFReader{
+		Rc: res.Body,
+		Fn: func() { t.setModReq(req, nil) },
+	}
+	return res, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (t *transport) CancelRequest(req *http.Request) {
+	type canceler interface {
+		CancelRequest(*http.Request)
+	}
+	if cr, ok := t.base().(canceler); ok {
+		t.mu.Lock()
+		modReq := t.modReq[req]
+		delete(t.modReq, req)
+		t.mu.Unlock()
+		cr.CancelRequest(modReq)
+	}
+}
+
+func (t *transport) base() http.RoundTripper {
+	if t.Base != nil {
+		return t.Base
+	}
+	return http.DefaultTransport
+}
+
+func (t *transport) setModReq(orig, mod *http.Request) {
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	if t.modReq == nil {
+		t.modReq = make(map[*http.Request]*http.Request)
+	}
+	if mod == nil {
+		delete(t.modReq, orig)
+	} else {
+		t.modReq[orig] = mod
+	}
+}
+
+// CloneRequest returns a clone of the provided *http.Request.
+// The clone is a shallow copy of the struct and its Header map.
+func CloneRequest(r *http.Request) *http.Request {
+	// shallow copy of the struct
+	r2 := new(http.Request)
+	*r2 = *r
+	// deep copy of the Header
+	r2.Header = make(http.Header, len(r.Header))
+	for k, s := range r.Header {
+		r2.Header[k] = append([]string(nil), s...)
+	}
+
+	return r2
+}
+
+// OnEOFReader ensures a callback function is called
+// on Close() and when the underlying Reader returns an io.EOF error
+type OnEOFReader struct {
+	Rc io.ReadCloser
+	Fn func()
+}
+
+func (r *OnEOFReader) Read(p []byte) (n int, err error) {
+	n, err = r.Rc.Read(p)
+	if err == io.EOF {
+		r.runFunc()
+	}
+	return
+}
+
+func (r *OnEOFReader) Close() error {
+	err := r.Rc.Close()
+	r.runFunc()
+	return err
+}
+
+func (r *OnEOFReader) runFunc() {
+	if fn := r.Fn; fn != nil {
+		fn()
+		r.Fn = nil
+	}
+}

+ 1 - 0
pkg/useragent/README.md

@@ -0,0 +1 @@
+This package provides helper functions to pack version information into a single User-Agent header.

+ 60 - 0
pkg/useragent/useragent.go

@@ -0,0 +1,60 @@
+// Package useragent provides helper functions to pack
+// version information into a single User-Agent header.
+package useragent
+
+import (
+	"errors"
+	"strings"
+)
+
+var (
+	ErrNilRequest = errors.New("request cannot be nil")
+)
+
+// VersionInfo is used to model UserAgent versions.
+type VersionInfo struct {
+	Name    string
+	Version string
+}
+
+func (vi *VersionInfo) isValid() bool {
+	const stopChars = " \t\r\n/"
+	name := vi.Name
+	vers := vi.Version
+	if len(name) == 0 || strings.ContainsAny(name, stopChars) {
+		return false
+	}
+	if len(vers) == 0 || strings.ContainsAny(vers, stopChars) {
+		return false
+	}
+	return true
+}
+
+// Convert versions to a string and append the string to the string base.
+//
+// Each VersionInfo will be converted to a string in the format of
+// "product/version", where the "product" is get from the name field, while
+// version is get from the version field. Several pieces of verson information
+// will be concatinated and separated by space.
+//
+// Example:
+// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
+// results in "base foo/1.0 bar/2.0".
+func AppendVersions(base string, versions ...VersionInfo) string {
+	if len(versions) == 0 {
+		return base
+	}
+
+	verstrs := make([]string, 0, 1+len(versions))
+	if len(base) > 0 {
+		verstrs = append(verstrs, base)
+	}
+
+	for _, v := range versions {
+		if !v.isValid() {
+			continue
+		}
+		verstrs = append(verstrs, v.Name+"/"+v.Version)
+	}
+	return strings.Join(verstrs, " ")
+}

+ 31 - 0
pkg/useragent/useragent_test.go

@@ -0,0 +1,31 @@
+package useragent
+
+import "testing"
+
+func TestVersionInfo(t *testing.T) {
+	vi := VersionInfo{"foo", "bar"}
+	if !vi.isValid() {
+		t.Fatalf("VersionInfo should be valid")
+	}
+	vi = VersionInfo{"", "bar"}
+	if vi.isValid() {
+		t.Fatalf("Expected VersionInfo to be invalid")
+	}
+	vi = VersionInfo{"foo", ""}
+	if vi.isValid() {
+		t.Fatalf("Expected VersionInfo to be invalid")
+	}
+}
+
+func TestAppendVersions(t *testing.T) {
+	vis := []VersionInfo{
+		{"foo", "1.0"},
+		{"bar", "0.1"},
+		{"pi", "3.1.4"},
+	}
+	v := AppendVersions("base", vis...)
+	expect := "base foo/1.0 bar/0.1 pi/3.1.4"
+	if v != expect {
+		t.Fatalf("expected %q, got %q", expect, v)
+	}
+}

+ 20 - 26
registry/auth.go

@@ -11,7 +11,6 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
-	"github.com/docker/docker/pkg/requestdecorator"
 )
 )
 
 
 type RequestAuthorization struct {
 type RequestAuthorization struct {
@@ -45,9 +44,6 @@ func (auth *RequestAuthorization) getToken() (string, error) {
 		return auth.tokenCache, nil
 		return auth.tokenCache, nil
 	}
 	}
 
 
-	client := auth.registryEndpoint.HTTPClient()
-	factory := HTTPRequestFactory(nil)
-
 	for _, challenge := range auth.registryEndpoint.AuthChallenges {
 	for _, challenge := range auth.registryEndpoint.AuthChallenges {
 		switch strings.ToLower(challenge.Scheme) {
 		switch strings.ToLower(challenge.Scheme) {
 		case "basic":
 		case "basic":
@@ -59,7 +55,7 @@ func (auth *RequestAuthorization) getToken() (string, error) {
 				params[k] = v
 				params[k] = v
 			}
 			}
 			params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
 			params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
-			token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint, client, factory)
+			token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint)
 			if err != nil {
 			if err != nil {
 				return "", err
 				return "", err
 			}
 			}
@@ -92,21 +88,20 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error {
 }
 }
 
 
 // Login tries to register/login to the registry server.
 // Login tries to register/login to the registry server.
-func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
+func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
 	// Separates the v2 registry login logic from the v1 logic.
 	// Separates the v2 registry login logic from the v1 logic.
 	if registryEndpoint.Version == APIVersion2 {
 	if registryEndpoint.Version == APIVersion2 {
-		return loginV2(authConfig, registryEndpoint, factory)
+		return loginV2(authConfig, registryEndpoint)
 	}
 	}
-	return loginV1(authConfig, registryEndpoint, factory)
+	return loginV1(authConfig, registryEndpoint)
 }
 }
 
 
 // loginV1 tries to register/login to the v1 registry server.
 // loginV1 tries to register/login to the v1 registry server.
-func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
+func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
 	var (
 	var (
 		status        string
 		status        string
 		reqBody       []byte
 		reqBody       []byte
 		err           error
 		err           error
-		client        = registryEndpoint.HTTPClient()
 		reqStatusCode = 0
 		reqStatusCode = 0
 		serverAddress = authConfig.ServerAddress
 		serverAddress = authConfig.ServerAddress
 	)
 	)
@@ -130,7 +125,7 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 
 
 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 	b := strings.NewReader(string(jsonBody))
 	b := strings.NewReader(string(jsonBody))
-	req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
+	req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
 	if err != nil {
 	if err != nil {
 		return "", fmt.Errorf("Server Error: %s", err)
 		return "", fmt.Errorf("Server Error: %s", err)
 	}
 	}
@@ -151,9 +146,9 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 		}
 		}
 	} else if reqStatusCode == 400 {
 	} else if reqStatusCode == 400 {
 		if string(reqBody) == "\"Username or email already exists\"" {
 		if string(reqBody) == "\"Username or email already exists\"" {
-			req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+			req, err := http.NewRequest("GET", serverAddress+"users/", nil)
 			req.SetBasicAuth(authConfig.Username, authConfig.Password)
 			req.SetBasicAuth(authConfig.Username, authConfig.Password)
-			resp, err := client.Do(req)
+			resp, err := registryEndpoint.client.Do(req)
 			if err != nil {
 			if err != nil {
 				return "", err
 				return "", err
 			}
 			}
@@ -180,9 +175,9 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 	} else if reqStatusCode == 401 {
 	} else if reqStatusCode == 401 {
 		// This case would happen with private registries where /v1/users is
 		// This case would happen with private registries where /v1/users is
 		// protected, so people can use `docker login` as an auth check.
 		// protected, so people can use `docker login` as an auth check.
-		req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+		req, err := http.NewRequest("GET", serverAddress+"users/", nil)
 		req.SetBasicAuth(authConfig.Username, authConfig.Password)
 		req.SetBasicAuth(authConfig.Username, authConfig.Password)
-		resp, err := client.Do(req)
+		resp, err := registryEndpoint.client.Do(req)
 		if err != nil {
 		if err != nil {
 			return "", err
 			return "", err
 		}
 		}
@@ -214,12 +209,11 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 // now, users should create their account through other means like directly from a web page
 // now, users should create their account through other means like directly from a web page
 // served by the v2 registry service provider. Whether this will be supported in the future
 // served by the v2 registry service provider. Whether this will be supported in the future
 // is to be determined.
 // is to be determined.
-func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) {
+func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
 	logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
 	logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
 	var (
 	var (
 		err       error
 		err       error
 		allErrors []error
 		allErrors []error
-		client    = registryEndpoint.HTTPClient()
 	)
 	)
 
 
 	for _, challenge := range registryEndpoint.AuthChallenges {
 	for _, challenge := range registryEndpoint.AuthChallenges {
@@ -227,9 +221,9 @@ func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 
 
 		switch strings.ToLower(challenge.Scheme) {
 		switch strings.ToLower(challenge.Scheme) {
 		case "basic":
 		case "basic":
-			err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+			err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
 		case "bearer":
 		case "bearer":
-			err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+			err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
 		default:
 		default:
 			// Unsupported challenge types are explicitly skipped.
 			// Unsupported challenge types are explicitly skipped.
 			err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
 			err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
@@ -247,15 +241,15 @@ func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, facto
 	return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
 	return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
 }
 }
 
 
-func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
-	req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+	req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
 
 
-	resp, err := client.Do(req)
+	resp, err := registryEndpoint.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -268,20 +262,20 @@ func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]str
 	return nil
 	return nil
 }
 }
 
 
-func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error {
-	token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
+func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+	token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+	req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
 	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
 
 
-	resp, err := client.Do(req)
+	resp, err := registryEndpoint.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 19 - 35
registry/endpoint.go

@@ -1,7 +1,6 @@
 package registry
 package registry
 
 
 import (
 import (
-	"crypto/tls"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
@@ -12,7 +11,7 @@ import (
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/registry/api/v2"
 	"github.com/docker/distribution/registry/api/v2"
-	"github.com/docker/docker/pkg/requestdecorator"
+	"github.com/docker/docker/pkg/transport"
 )
 )
 
 
 // for mocking in unit tests
 // for mocking in unit tests
@@ -43,9 +42,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
 }
 }
 
 
 // NewEndpoint parses the given address to return a registry endpoint.
 // NewEndpoint parses the given address to return a registry endpoint.
-func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
+func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
 	// *TODO: Allow per-registry configuration of endpoints.
 	// *TODO: Allow per-registry configuration of endpoints.
-	endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
+	endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, metaHeaders)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -83,7 +82,7 @@ func validateEndpoint(endpoint *Endpoint) error {
 	return nil
 	return nil
 }
 }
 
 
-func newEndpoint(address string, secure bool) (*Endpoint, error) {
+func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoint, error) {
 	var (
 	var (
 		endpoint       = new(Endpoint)
 		endpoint       = new(Endpoint)
 		trimmedAddress string
 		trimmedAddress string
@@ -100,15 +99,18 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	endpoint.IsSecure = secure
 	endpoint.IsSecure = secure
+	tr := NewTransport(ConnectTimeout, endpoint.IsSecure)
+	endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
 	return endpoint, nil
 	return endpoint, nil
 }
 }
 
 
-func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
-	return NewEndpoint(repoInfo.Index)
+func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
+	return NewEndpoint(repoInfo.Index, metaHeaders)
 }
 }
 
 
 // Endpoint stores basic information about a registry endpoint.
 // Endpoint stores basic information about a registry endpoint.
 type Endpoint struct {
 type Endpoint struct {
+	client         *http.Client
 	URL            *url.URL
 	URL            *url.URL
 	Version        APIVersion
 	Version        APIVersion
 	IsSecure       bool
 	IsSecure       bool
@@ -135,25 +137,24 @@ func (e *Endpoint) Path(path string) string {
 
 
 func (e *Endpoint) Ping() (RegistryInfo, error) {
 func (e *Endpoint) Ping() (RegistryInfo, error) {
 	// The ping logic to use is determined by the registry endpoint version.
 	// The ping logic to use is determined by the registry endpoint version.
-	factory := HTTPRequestFactory(nil)
 	switch e.Version {
 	switch e.Version {
 	case APIVersion1:
 	case APIVersion1:
-		return e.pingV1(factory)
+		return e.pingV1()
 	case APIVersion2:
 	case APIVersion2:
-		return e.pingV2(factory)
+		return e.pingV2()
 	}
 	}
 
 
 	// APIVersionUnknown
 	// APIVersionUnknown
 	// We should try v2 first...
 	// We should try v2 first...
 	e.Version = APIVersion2
 	e.Version = APIVersion2
-	regInfo, errV2 := e.pingV2(factory)
+	regInfo, errV2 := e.pingV2()
 	if errV2 == nil {
 	if errV2 == nil {
 		return regInfo, nil
 		return regInfo, nil
 	}
 	}
 
 
 	// ... then fallback to v1.
 	// ... then fallback to v1.
 	e.Version = APIVersion1
 	e.Version = APIVersion1
-	regInfo, errV1 := e.pingV1(factory)
+	regInfo, errV1 := e.pingV1()
 	if errV1 == nil {
 	if errV1 == nil {
 		return regInfo, nil
 		return regInfo, nil
 	}
 	}
@@ -162,7 +163,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
 	return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
 	return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1)
 }
 }
 
 
-func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInfo, error) {
+func (e *Endpoint) pingV1() (RegistryInfo, error) {
 	logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
 	logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
 
 
 	if e.String() == IndexServerAddress() {
 	if e.String() == IndexServerAddress() {
@@ -171,12 +172,12 @@ func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInf
 		return RegistryInfo{Standalone: false}, nil
 		return RegistryInfo{Standalone: false}, nil
 	}
 	}
 
 
-	req, err := factory.NewRequest("GET", e.Path("_ping"), nil)
+	req, err := http.NewRequest("GET", e.Path("_ping"), nil)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{Standalone: false}, err
 		return RegistryInfo{Standalone: false}, err
 	}
 	}
 
 
-	resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+	resp, err := e.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{Standalone: false}, err
 		return RegistryInfo{Standalone: false}, err
 	}
 	}
@@ -216,15 +217,15 @@ func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInf
 	return info, nil
 	return info, nil
 }
 }
 
 
-func (e *Endpoint) pingV2(factory *requestdecorator.RequestFactory) (RegistryInfo, error) {
+func (e *Endpoint) pingV2() (RegistryInfo, error) {
 	logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
 	logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
 
 
-	req, err := factory.NewRequest("GET", e.Path(""), nil)
+	req, err := http.NewRequest("GET", e.Path(""), nil)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{}, err
 		return RegistryInfo{}, err
 	}
 	}
 
 
-	resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+	resp, err := e.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{}, err
 		return RegistryInfo{}, err
 	}
 	}
@@ -263,20 +264,3 @@ HeaderLoop:
 
 
 	return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
 	return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode))
 }
 }
-
-func (e *Endpoint) HTTPClient() *http.Client {
-	tlsConfig := tls.Config{
-		MinVersion: tls.VersionTLS10,
-	}
-	if !e.IsSecure {
-		tlsConfig.InsecureSkipVerify = true
-	}
-	return &http.Client{
-		Transport: &http.Transport{
-			DisableKeepAlives: true,
-			Proxy:             http.ProxyFromEnvironment,
-			TLSClientConfig:   &tlsConfig,
-		},
-		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
-	}
-}

+ 2 - 1
registry/endpoint_test.go

@@ -19,7 +19,7 @@ func TestEndpointParse(t *testing.T) {
 		{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
 		{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
 	}
 	}
 	for _, td := range testData {
 	for _, td := range testData {
-		e, err := newEndpoint(td.str, false)
+		e, err := newEndpoint(td.str, false, nil)
 		if err != nil {
 		if err != nil {
 			t.Errorf("%q: %s", td.str, err)
 			t.Errorf("%q: %s", td.str, err)
 		}
 		}
@@ -60,6 +60,7 @@ func TestValidateEndpointAmbiguousAPIVersion(t *testing.T) {
 	testEndpoint := Endpoint{
 	testEndpoint := Endpoint{
 		URL:     testServerURL,
 		URL:     testServerURL,
 		Version: APIVersionUnknown,
 		Version: APIVersionUnknown,
+		client:  HTTPClient(NewTransport(ConnectTimeout, false)),
 	}
 	}
 
 
 	if err = validateEndpoint(&testEndpoint); err != nil {
 	if err = validateEndpoint(&testEndpoint); err != nil {

+ 0 - 30
registry/httpfactory.go

@@ -1,30 +0,0 @@
-package registry
-
-import (
-	"runtime"
-
-	"github.com/docker/docker/autogen/dockerversion"
-	"github.com/docker/docker/pkg/parsers/kernel"
-	"github.com/docker/docker/pkg/requestdecorator"
-)
-
-func HTTPRequestFactory(metaHeaders map[string][]string) *requestdecorator.RequestFactory {
-	// FIXME: this replicates the 'info' job.
-	httpVersion := make([]requestdecorator.UAVersionInfo, 0, 4)
-	httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("docker", dockerversion.VERSION))
-	httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("go", runtime.Version()))
-	httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("git-commit", dockerversion.GITCOMMIT))
-	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
-		httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("kernel", kernelVersion.String()))
-	}
-	httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("os", runtime.GOOS))
-	httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("arch", runtime.GOARCH))
-	uad := &requestdecorator.UserAgentDecorator{
-		Versions: httpVersion,
-	}
-	mhd := &requestdecorator.MetaHeadersDecorator{
-		Headers: metaHeaders,
-	}
-	factory := requestdecorator.NewRequestFactory(uad, mhd)
-	return factory
-}

+ 131 - 69
registry/registry.go

@@ -8,13 +8,19 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
+	"net/http/httputil"
 	"os"
 	"os"
 	"path"
 	"path"
+	"runtime"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/docker/autogen/dockerversion"
+	"github.com/docker/docker/pkg/parsers/kernel"
 	"github.com/docker/docker/pkg/timeoutconn"
 	"github.com/docker/docker/pkg/timeoutconn"
+	"github.com/docker/docker/pkg/transport"
+	"github.com/docker/docker/pkg/useragent"
 )
 )
 
 
 var (
 var (
@@ -31,66 +37,38 @@ const (
 	ConnectTimeout
 	ConnectTimeout
 )
 )
 
 
-func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
-	tlsConfig := tls.Config{
-		RootCAs: roots,
-		// Avoid fallback to SSL protocols < TLS1.0
-		MinVersion:   tls.VersionTLS10,
-		Certificates: certs,
-	}
-
-	if !secure {
-		tlsConfig.InsecureSkipVerify = true
-	}
-
-	httpTransport := &http.Transport{
-		DisableKeepAlives: true,
-		Proxy:             http.ProxyFromEnvironment,
-		TLSClientConfig:   &tlsConfig,
+// dockerUserAgent is the User-Agent the Docker client uses to identify itself.
+// It is populated on init(), comprising version information of different components.
+var dockerUserAgent string
+
+func init() {
+	httpVersion := make([]useragent.VersionInfo, 0, 6)
+	httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
+	httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
+	httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
+	if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
+		httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
 	}
 	}
+	httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
+	httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
 
 
-	switch timeout {
-	case ConnectTimeout:
-		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
-			// Set the connect timeout to 30 seconds to allow for slower connection
-			// times...
-			d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
-
-			conn, err := d.Dial(proto, addr)
-			if err != nil {
-				return nil, err
-			}
-			// Set the recv timeout to 10 seconds
-			conn.SetDeadline(time.Now().Add(10 * time.Second))
-			return conn, nil
-		}
-	case ReceiveTimeout:
-		httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
-			d := net.Dialer{DualStack: true}
-
-			conn, err := d.Dial(proto, addr)
-			if err != nil {
-				return nil, err
-			}
-			conn = timeoutconn.New(conn, 1*time.Minute)
-			return conn, nil
-		}
-	}
-
-	return &http.Client{
-		Transport:     httpTransport,
-		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
-		Jar:           jar,
-	}
+	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
 }
 }
 
 
-func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
+type httpsRequestModifier struct{ tlsConfig *tls.Config }
+
+// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
+// it's because it's so as to match the current behavior in master: we generate the
+// certpool on every-goddam-request. It's not great, but it allows people to just put
+// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
+// prefer an fsnotify implementation, but that was out of scope of my refactoring.
+func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
 	var (
 	var (
-		pool  *x509.CertPool
+		roots *x509.CertPool
 		certs []tls.Certificate
 		certs []tls.Certificate
 	)
 	)
 
 
-	if secure && req.URL.Scheme == "https" {
+	if req.URL.Scheme == "https" {
 		hasFile := func(files []os.FileInfo, name string) bool {
 		hasFile := func(files []os.FileInfo, name string) bool {
 			for _, f := range files {
 			for _, f := range files {
 				if f.Name() == name {
 				if f.Name() == name {
@@ -104,31 +82,31 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
 		logrus.Debugf("hostDir: %s", hostDir)
 		logrus.Debugf("hostDir: %s", hostDir)
 		fs, err := ioutil.ReadDir(hostDir)
 		fs, err := ioutil.ReadDir(hostDir)
 		if err != nil && !os.IsNotExist(err) {
 		if err != nil && !os.IsNotExist(err) {
-			return nil, nil, err
+			return nil
 		}
 		}
 
 
 		for _, f := range fs {
 		for _, f := range fs {
 			if strings.HasSuffix(f.Name(), ".crt") {
 			if strings.HasSuffix(f.Name(), ".crt") {
-				if pool == nil {
-					pool = x509.NewCertPool()
+				if roots == nil {
+					roots = x509.NewCertPool()
 				}
 				}
 				logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
 				logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
 				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
 				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
 				if err != nil {
 				if err != nil {
-					return nil, nil, err
+					return err
 				}
 				}
-				pool.AppendCertsFromPEM(data)
+				roots.AppendCertsFromPEM(data)
 			}
 			}
 			if strings.HasSuffix(f.Name(), ".cert") {
 			if strings.HasSuffix(f.Name(), ".cert") {
 				certName := f.Name()
 				certName := f.Name()
 				keyName := certName[:len(certName)-5] + ".key"
 				keyName := certName[:len(certName)-5] + ".key"
 				logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
 				logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
 				if !hasFile(fs, keyName) {
 				if !hasFile(fs, keyName) {
-					return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
+					return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
 				}
 				}
 				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
 				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
 				if err != nil {
 				if err != nil {
-					return nil, nil, err
+					return err
 				}
 				}
 				certs = append(certs, cert)
 				certs = append(certs, cert)
 			}
 			}
@@ -137,24 +115,108 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
 				certName := keyName[:len(keyName)-4] + ".cert"
 				certName := keyName[:len(keyName)-4] + ".cert"
 				logrus.Debugf("key: %s", hostDir+"/"+f.Name())
 				logrus.Debugf("key: %s", hostDir+"/"+f.Name())
 				if !hasFile(fs, certName) {
 				if !hasFile(fs, certName) {
-					return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
+					return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
 				}
 				}
 			}
 			}
 		}
 		}
+		m.tlsConfig.RootCAs = roots
+		m.tlsConfig.Certificates = certs
 	}
 	}
+	return nil
+}
 
 
-	if len(certs) == 0 {
-		client := newClient(jar, pool, nil, timeout, secure)
-		res, err := client.Do(req)
-		if err != nil {
-			return nil, nil, err
+func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
+	tlsConfig := &tls.Config{
+		// Avoid fallback to SSL protocols < TLS1.0
+		MinVersion:         tls.VersionTLS10,
+		InsecureSkipVerify: !secure,
+	}
+
+	tr := &http.Transport{
+		DisableKeepAlives: true,
+		Proxy:             http.ProxyFromEnvironment,
+		TLSClientConfig:   tlsConfig,
+	}
+
+	switch timeout {
+	case ConnectTimeout:
+		tr.Dial = func(proto string, addr string) (net.Conn, error) {
+			// Set the connect timeout to 30 seconds to allow for slower connection
+			// times...
+			d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
+
+			conn, err := d.Dial(proto, addr)
+			if err != nil {
+				return nil, err
+			}
+			// Set the recv timeout to 10 seconds
+			conn.SetDeadline(time.Now().Add(10 * time.Second))
+			return conn, nil
+		}
+	case ReceiveTimeout:
+		tr.Dial = func(proto string, addr string) (net.Conn, error) {
+			d := net.Dialer{DualStack: true}
+
+			conn, err := d.Dial(proto, addr)
+			if err != nil {
+				return nil, err
+			}
+			conn = timeoutconn.New(conn, 1*time.Minute)
+			return conn, nil
 		}
 		}
-		return res, client, nil
 	}
 	}
 
 
-	client := newClient(jar, pool, certs, timeout, secure)
-	res, err := client.Do(req)
-	return res, client, err
+	if secure {
+		// note: httpsTransport also handles http transport
+		// but for HTTPS, it sets up the certs
+		return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig})
+	}
+
+	return tr
+}
+
+// DockerHeaders returns request modifiers that ensure requests have
+// the User-Agent header set to dockerUserAgent and that metaHeaders
+// are added.
+func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
+	modifiers := []transport.RequestModifier{
+		transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
+	}
+	if metaHeaders != nil {
+		modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
+	}
+	return modifiers
+}
+
+type debugTransport struct{ http.RoundTripper }
+
+func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	dump, err := httputil.DumpRequestOut(req, false)
+	if err != nil {
+		fmt.Println("could not dump request")
+	}
+	fmt.Println(string(dump))
+	resp, err := tr.RoundTripper.RoundTrip(req)
+	if err != nil {
+		return nil, err
+	}
+	dump, err = httputil.DumpResponse(resp, false)
+	if err != nil {
+		fmt.Println("could not dump response")
+	}
+	fmt.Println(string(dump))
+	return resp, err
+}
+
+func HTTPClient(transport http.RoundTripper) *http.Client {
+	if transport == nil {
+		transport = NewTransport(ConnectTimeout, true)
+	}
+
+	return &http.Client{
+		Transport:     transport,
+		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
+	}
 }
 }
 
 
 func trustedLocation(req *http.Request) bool {
 func trustedLocation(req *http.Request) bool {

+ 34 - 45
registry/registry_test.go

@@ -8,7 +8,7 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
-	"github.com/docker/docker/pkg/requestdecorator"
+	"github.com/docker/docker/pkg/transport"
 )
 )
 
 
 var (
 var (
@@ -22,45 +22,34 @@ const (
 
 
 func spawnTestRegistrySession(t *testing.T) *Session {
 func spawnTestRegistrySession(t *testing.T) *Session {
 	authConfig := &cliconfig.AuthConfig{}
 	authConfig := &cliconfig.AuthConfig{}
-	endpoint, err := NewEndpoint(makeIndex("/v1/"))
+	endpoint, err := NewEndpoint(makeIndex("/v1/"), nil)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true)
+	var tr http.RoundTripper = debugTransport{NewTransport(ReceiveTimeout, endpoint.IsSecure)}
+	tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
+	client := HTTPClient(tr)
+	r, err := NewSession(client, authConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
+	// In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
+	// header while authenticating, in order to retrieve a token that can be later used to
+	// perform authenticated actions.
+	//
+	// The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
+	// it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
+	//
+	// Because we know that the client's transport is an `*authTransport` we simply cast it,
+	// in order to set the internal cached token to the fake token, and thus send that fake token
+	// upon every subsequent requests.
+	r.client.Transport.(*authTransport).token = token
 	return r
 	return r
 }
 }
 
 
-func TestPublicSession(t *testing.T) {
-	authConfig := &cliconfig.AuthConfig{}
-
-	getSessionDecorators := func(index *IndexInfo) int {
-		endpoint, err := NewEndpoint(index)
-		if err != nil {
-			t.Fatal(err)
-		}
-		r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true)
-		if err != nil {
-			t.Fatal(err)
-		}
-		return len(r.reqFactory.GetDecorators())
-	}
-
-	decorators := getSessionDecorators(makeIndex("/v1/"))
-	assertEqual(t, decorators, 0, "Expected no decorator on http session")
-
-	decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
-	assertNotEqual(t, decorators, 0, "Expected decorator on https session")
-
-	decorators = getSessionDecorators(makePublicIndex())
-	assertEqual(t, decorators, 0, "Expected no decorator on public session")
-}
-
 func TestPingRegistryEndpoint(t *testing.T) {
 func TestPingRegistryEndpoint(t *testing.T) {
 	testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
 	testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
-		ep, err := NewEndpoint(index)
+		ep, err := NewEndpoint(index, nil)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -80,7 +69,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
 func TestEndpoint(t *testing.T) {
 func TestEndpoint(t *testing.T) {
 	// Simple wrapper to fail test if err != nil
 	// Simple wrapper to fail test if err != nil
 	expandEndpoint := func(index *IndexInfo) *Endpoint {
 	expandEndpoint := func(index *IndexInfo) *Endpoint {
-		endpoint, err := NewEndpoint(index)
+		endpoint, err := NewEndpoint(index, nil)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
@@ -89,7 +78,7 @@ func TestEndpoint(t *testing.T) {
 
 
 	assertInsecureIndex := func(index *IndexInfo) {
 	assertInsecureIndex := func(index *IndexInfo) {
 		index.Secure = true
 		index.Secure = true
-		_, err := NewEndpoint(index)
+		_, err := NewEndpoint(index, nil)
 		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
 		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
 		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
 		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
 		index.Secure = false
 		index.Secure = false
@@ -97,7 +86,7 @@ func TestEndpoint(t *testing.T) {
 
 
 	assertSecureIndex := func(index *IndexInfo) {
 	assertSecureIndex := func(index *IndexInfo) {
 		index.Secure = true
 		index.Secure = true
-		_, err := NewEndpoint(index)
+		_, err := NewEndpoint(index, nil)
 		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
 		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
 		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
 		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
 		index.Secure = false
 		index.Secure = false
@@ -163,14 +152,14 @@ func TestEndpoint(t *testing.T) {
 	}
 	}
 	for _, address := range badEndpoints {
 	for _, address := range badEndpoints {
 		index.Name = address
 		index.Name = address
-		_, err := NewEndpoint(index)
+		_, err := NewEndpoint(index, nil)
 		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
 		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
 	}
 	}
 }
 }
 
 
 func TestGetRemoteHistory(t *testing.T) {
 func TestGetRemoteHistory(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"), token)
+	hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -182,16 +171,16 @@ func TestGetRemoteHistory(t *testing.T) {
 
 
 func TestLookupRemoteImage(t *testing.T) {
 func TestLookupRemoteImage(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	err := r.LookupRemoteImage(imageID, makeURL("/v1/"), token)
+	err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
 	assertEqual(t, err, nil, "Expected error of remote lookup to nil")
 	assertEqual(t, err, nil, "Expected error of remote lookup to nil")
-	if err := r.LookupRemoteImage("abcdef", makeURL("/v1/"), token); err == nil {
+	if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
 		t.Fatal("Expected error of remote lookup to not nil")
 		t.Fatal("Expected error of remote lookup to not nil")
 	}
 	}
 }
 }
 
 
 func TestGetRemoteImageJSON(t *testing.T) {
 func TestGetRemoteImageJSON(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"), token)
+	json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -200,7 +189,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
 		t.Fatal("Expected non-empty json")
 		t.Fatal("Expected non-empty json")
 	}
 	}
 
 
-	_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"), token)
+	_, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
 	if err == nil {
 	if err == nil {
 		t.Fatal("Expected image not found error")
 		t.Fatal("Expected image not found error")
 	}
 	}
@@ -208,7 +197,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
 
 
 func TestGetRemoteImageLayer(t *testing.T) {
 func TestGetRemoteImageLayer(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), token, 0)
+	data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -216,7 +205,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
 		t.Fatal("Expected non-nil data result")
 		t.Fatal("Expected non-nil data result")
 	}
 	}
 
 
-	_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), token, 0)
+	_, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
 	if err == nil {
 	if err == nil {
 		t.Fatal("Expected image not found error")
 		t.Fatal("Expected image not found error")
 	}
 	}
@@ -224,14 +213,14 @@ func TestGetRemoteImageLayer(t *testing.T) {
 
 
 func TestGetRemoteTags(t *testing.T) {
 func TestGetRemoteTags(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, token)
+	tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	assertEqual(t, len(tags), 1, "Expected one tag")
 	assertEqual(t, len(tags), 1, "Expected one tag")
 	assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
 	assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
 
 
-	_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", token)
+	_, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
 	if err == nil {
 	if err == nil {
 		t.Fatal("Expected error when fetching tags for bogus repo")
 		t.Fatal("Expected error when fetching tags for bogus repo")
 	}
 	}
@@ -265,7 +254,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
 		Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
 		Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
 	}
 	}
 
 
-	err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"), token)
+	err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -274,7 +263,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
 func TestPushImageLayerRegistry(t *testing.T) {
 func TestPushImageLayerRegistry(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
 	layer := strings.NewReader("")
 	layer := strings.NewReader("")
-	_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), token, []byte{})
+	_, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -694,7 +683,7 @@ func TestNewIndexInfo(t *testing.T) {
 
 
 func TestPushRegistryTag(t *testing.T) {
 func TestPushRegistryTag(t *testing.T) {
 	r := spawnTestRegistrySession(t)
 	r := spawnTestRegistrySession(t)
-	err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"), token)
+	err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 10 - 5
registry/service.go

@@ -1,6 +1,10 @@
 package registry
 package registry
 
 
-import "github.com/docker/docker/cliconfig"
+import (
+	"net/http"
+
+	"github.com/docker/docker/cliconfig"
+)
 
 
 type Service struct {
 type Service struct {
 	Config *ServiceConfig
 	Config *ServiceConfig
@@ -27,12 +31,12 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	endpoint, err := NewEndpoint(index)
+	endpoint, err := NewEndpoint(index, nil)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 	authConfig.ServerAddress = endpoint.String()
 	authConfig.ServerAddress = endpoint.String()
-	return Login(authConfig, endpoint, HTTPRequestFactory(nil))
+	return Login(authConfig, endpoint)
 }
 }
 
 
 // Search queries the public registry for images matching the specified
 // Search queries the public registry for images matching the specified
@@ -42,12 +46,13 @@ func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
 	// *TODO: Search multiple indexes.
 	// *TODO: Search multiple indexes.
-	endpoint, err := repoInfo.GetEndpoint()
+	endpoint, err := repoInfo.GetEndpoint(http.Header(headers))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	r, err := NewSession(authConfig, HTTPRequestFactory(headers), endpoint, true)
+	r, err := NewSession(endpoint.client, authConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 179 - 144
registry/session.go

@@ -3,6 +3,8 @@ package registry
 import (
 import (
 	"bytes"
 	"bytes"
 	"crypto/sha256"
 	"crypto/sha256"
+	"errors"
+	"sync"
 	// this is required for some certificates
 	// this is required for some certificates
 	_ "crypto/sha512"
 	_ "crypto/sha512"
 	"encoding/hex"
 	"encoding/hex"
@@ -20,64 +22,143 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/pkg/httputils"
 	"github.com/docker/docker/pkg/httputils"
-	"github.com/docker/docker/pkg/requestdecorator"
 	"github.com/docker/docker/pkg/tarsum"
 	"github.com/docker/docker/pkg/tarsum"
+	"github.com/docker/docker/pkg/transport"
 )
 )
 
 
 type Session struct {
 type Session struct {
-	authConfig    *cliconfig.AuthConfig
-	reqFactory    *requestdecorator.RequestFactory
 	indexEndpoint *Endpoint
 	indexEndpoint *Endpoint
-	jar           *cookiejar.Jar
-	timeout       TimeoutType
+	client        *http.Client
+	// TODO(tiborvass): remove authConfig
+	authConfig *cliconfig.AuthConfig
 }
 }
 
 
-func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
-	r = &Session{
-		authConfig:    authConfig,
-		indexEndpoint: endpoint,
+type authTransport struct {
+	http.RoundTripper
+	*cliconfig.AuthConfig
+
+	alwaysSetBasicAuth bool
+	token              []string
+
+	mu     sync.Mutex                      // guards modReq
+	modReq map[*http.Request]*http.Request // original -> modified
+}
+
+// AuthTransport handles the auth layer when communicating with a v1 registry (private or official)
+//
+// For private v1 registries, set alwaysSetBasicAuth to true.
+//
+// For the official v1 registry, if there isn't already an Authorization header in the request,
+// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header.
+// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing
+// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent
+// requests.
+//
+// If the server sends a token without the client having requested it, it is ignored.
+//
+// This RoundTripper also has a CancelRequest method important for correct timeout handling.
+func AuthTransport(base http.RoundTripper, authConfig *cliconfig.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper {
+	if base == nil {
+		base = http.DefaultTransport
+	}
+	return &authTransport{
+		RoundTripper:       base,
+		AuthConfig:         authConfig,
+		alwaysSetBasicAuth: alwaysSetBasicAuth,
+		modReq:             make(map[*http.Request]*http.Request),
 	}
 	}
+}
 
 
-	if timeout {
-		r.timeout = ReceiveTimeout
+func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
+	req := transport.CloneRequest(orig)
+	tr.mu.Lock()
+	tr.modReq[orig] = req
+	tr.mu.Unlock()
+
+	if tr.alwaysSetBasicAuth {
+		req.SetBasicAuth(tr.Username, tr.Password)
+		return tr.RoundTripper.RoundTrip(req)
 	}
 	}
 
 
-	r.jar, err = cookiejar.New(nil)
+	var askedForToken bool
+
+	// Don't override
+	if req.Header.Get("Authorization") == "" {
+		if req.Header.Get("X-Docker-Token") == "true" {
+			req.SetBasicAuth(tr.Username, tr.Password)
+			askedForToken = true
+		} else if len(tr.token) > 0 {
+			req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
+		}
+	}
+	resp, err := tr.RoundTripper.RoundTrip(req)
 	if err != nil {
 	if err != nil {
+		delete(tr.modReq, orig)
 		return nil, err
 		return nil, err
 	}
 	}
+	if askedForToken && len(resp.Header["X-Docker-Token"]) > 0 {
+		tr.token = resp.Header["X-Docker-Token"]
+	}
+	resp.Body = &transport.OnEOFReader{
+		Rc: resp.Body,
+		Fn: func() { delete(tr.modReq, orig) },
+	}
+	return resp, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (tr *authTransport) CancelRequest(req *http.Request) {
+	type canceler interface {
+		CancelRequest(*http.Request)
+	}
+	if cr, ok := tr.RoundTripper.(canceler); ok {
+		tr.mu.Lock()
+		modReq := tr.modReq[req]
+		delete(tr.modReq, req)
+		tr.mu.Unlock()
+		cr.CancelRequest(modReq)
+	}
+}
+
+// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
+func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) {
+	r = &Session{
+		authConfig:    authConfig,
+		client:        client,
+		indexEndpoint: endpoint,
+	}
+
+	var alwaysSetBasicAuth bool
 
 
 	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
 	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
-	// alongside our requests.
-	if r.indexEndpoint.VersionString(1) != IndexServerAddress() && r.indexEndpoint.URL.Scheme == "https" {
-		info, err := r.indexEndpoint.Ping()
+	// alongside all our requests.
+	if endpoint.VersionString(1) != IndexServerAddress() && endpoint.URL.Scheme == "https" {
+		info, err := endpoint.Ping()
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-		if info.Standalone && authConfig != nil && factory != nil {
-			logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String())
-			dec := requestdecorator.NewAuthDecorator(authConfig.Username, authConfig.Password)
-			factory.AddDecorator(dec)
+
+		if info.Standalone && authConfig != nil {
+			logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
+			alwaysSetBasicAuth = true
 		}
 		}
 	}
 	}
 
 
-	r.reqFactory = factory
-	return r, nil
-}
+	client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
 
 
-func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
-	return doRequest(req, r.jar, r.timeout, r.indexEndpoint.IsSecure)
+	jar, err := cookiejar.New(nil)
+	if err != nil {
+		return nil, errors.New("cookiejar.New is not supposed to return an error")
+	}
+	client.Jar = jar
+
+	return r, nil
 }
 }
 
 
 // Retrieve the history of a given image from the Registry.
 // Retrieve the history of a given image from the Registry.
 // Return a list of the parent's json (requested image included)
 // Return a list of the parent's json (requested image included)
-func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
-	if err != nil {
-		return nil, err
-	}
-	setTokenAuth(req, token)
-	res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
+	res, err := r.client.Get(registry + "images/" + imgID + "/ancestry")
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -89,27 +170,18 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st
 		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
 		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res)
 	}
 	}
 
 
-	jsonString, err := ioutil.ReadAll(res.Body)
-	if err != nil {
-		return nil, fmt.Errorf("Error while reading the http response: %s", err)
+	var history []string
+	if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
+		return nil, fmt.Errorf("Error while reading the http response: %v", err)
 	}
 	}
 
 
-	logrus.Debugf("Ancestry: %s", jsonString)
-	history := new([]string)
-	if err := json.Unmarshal(jsonString, history); err != nil {
-		return nil, err
-	}
-	return *history, nil
+	logrus.Debugf("Ancestry: %v", history)
+	return history, nil
 }
 }
 
 
 // Check if an image exists in the Registry
 // Check if an image exists in the Registry
-func (r *Session) LookupRemoteImage(imgID, registry string, token []string) error {
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
-	if err != nil {
-		return err
-	}
-	setTokenAuth(req, token)
-	res, _, err := r.doRequest(req)
+func (r *Session) LookupRemoteImage(imgID, registry string) error {
+	res, err := r.client.Get(registry + "images/" + imgID + "/json")
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -121,14 +193,8 @@ func (r *Session) LookupRemoteImage(imgID, registry string, token []string) erro
 }
 }
 
 
 // Retrieve an image from the Registry.
 // Retrieve an image from the Registry.
-func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
-	// Get the JSON
-	req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
-	if err != nil {
-		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
-	}
-	setTokenAuth(req, token)
-	res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) {
+	res, err := r.client.Get(registry + "images/" + imgID + "/json")
 	if err != nil {
 	if err != nil {
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 	}
 	}
@@ -147,44 +213,44 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]
 
 
 	jsonString, err := ioutil.ReadAll(res.Body)
 	jsonString, err := ioutil.ReadAll(res.Body)
 	if err != nil {
 	if err != nil {
-		return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
+		return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
 	}
 	}
 	return jsonString, imageSize, nil
 	return jsonString, imageSize, nil
 }
 }
 
 
-func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
+func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
 	var (
 	var (
 		retries    = 5
 		retries    = 5
 		statusCode = 0
 		statusCode = 0
-		client     *http.Client
 		res        *http.Response
 		res        *http.Response
+		err        error
 		imageURL   = fmt.Sprintf("%simages/%s/layer", registry, imgID)
 		imageURL   = fmt.Sprintf("%simages/%s/layer", registry, imgID)
 	)
 	)
 
 
-	req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
+	req, err := http.NewRequest("GET", imageURL, nil)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
+		return nil, fmt.Errorf("Error while getting from the server: %v", err)
 	}
 	}
-	setTokenAuth(req, token)
+	// TODO: why are we doing retries at this level?
+	// These retries should be generic to both v1 and v2
 	for i := 1; i <= retries; i++ {
 	for i := 1; i <= retries; i++ {
 		statusCode = 0
 		statusCode = 0
-		res, client, err = r.doRequest(req)
-		if err != nil {
-			logrus.Debugf("Error contacting registry: %s", err)
-			if res != nil {
-				if res.Body != nil {
-					res.Body.Close()
-				}
-				statusCode = res.StatusCode
-			}
-			if i == retries {
-				return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
-					statusCode, imgID)
+		res, err = r.client.Do(req)
+		if err == nil {
+			break
+		}
+		logrus.Debugf("Error contacting registry %s: %v", registry, err)
+		if res != nil {
+			if res.Body != nil {
+				res.Body.Close()
 			}
 			}
-			time.Sleep(time.Duration(i) * 5 * time.Second)
-			continue
+			statusCode = res.StatusCode
+		}
+		if i == retries {
+			return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
+				statusCode, imgID)
 		}
 		}
-		break
+		time.Sleep(time.Duration(i) * 5 * time.Second)
 	}
 	}
 
 
 	if res.StatusCode != 200 {
 	if res.StatusCode != 200 {
@@ -195,13 +261,13 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im
 
 
 	if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
 	if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
 		logrus.Debugf("server supports resume")
 		logrus.Debugf("server supports resume")
-		return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
+		return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil
 	}
 	}
 	logrus.Debugf("server doesn't support resume")
 	logrus.Debugf("server doesn't support resume")
 	return res.Body, nil
 	return res.Body, nil
 }
 }
 
 
-func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
+func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
 	if strings.Count(repository, "/") == 0 {
 	if strings.Count(repository, "/") == 0 {
 		// This will be removed once the Registry supports auto-resolution on
 		// This will be removed once the Registry supports auto-resolution on
 		// the "library" namespace
 		// the "library" namespace
@@ -209,13 +275,7 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token []
 	}
 	}
 	for _, host := range registries {
 	for _, host := range registries {
 		endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
 		endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
-		req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
-
-		if err != nil {
-			return nil, err
-		}
-		setTokenAuth(req, token)
-		res, _, err := r.doRequest(req)
+		res, err := r.client.Get(endpoint)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
@@ -263,16 +323,13 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
 
 
 	logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
 	logrus.Debugf("[registry] Calling GET %s", repositoryTarget)
 
 
-	req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil)
+	req, err := http.NewRequest("GET", repositoryTarget, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
-		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
-	}
+	// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
 	req.Header.Set("X-Docker-Token", "true")
 	req.Header.Set("X-Docker-Token", "true")
-
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -292,11 +349,6 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
 		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
 		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
 	}
 	}
 
 
-	var tokens []string
-	if res.Header.Get("X-Docker-Token") != "" {
-		tokens = res.Header["X-Docker-Token"]
-	}
-
 	var endpoints []string
 	var endpoints []string
 	if res.Header.Get("X-Docker-Endpoints") != "" {
 	if res.Header.Get("X-Docker-Endpoints") != "" {
 		endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
 		endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
@@ -322,29 +374,29 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
 	return &RepositoryData{
 	return &RepositoryData{
 		ImgList:   imgsData,
 		ImgList:   imgsData,
 		Endpoints: endpoints,
 		Endpoints: endpoints,
-		Tokens:    tokens,
 	}, nil
 	}, nil
 }
 }
 
 
-func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
+func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
 
 
-	logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
+	u := registry + "images/" + imgData.ID + "/checksum"
 
 
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
+	logrus.Debugf("[registry] Calling PUT %s", u)
+
+	req, err := http.NewRequest("PUT", u, nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	setTokenAuth(req, token)
 	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
 	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
 	req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
 	req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
 
 
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
-		return fmt.Errorf("Failed to upload metadata: %s", err)
+		return fmt.Errorf("Failed to upload metadata: %v", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
 	if len(res.Cookies()) > 0 {
 	if len(res.Cookies()) > 0 {
-		r.jar.SetCookies(req.URL, res.Cookies())
+		r.client.Jar.SetCookies(req.URL, res.Cookies())
 	}
 	}
 	if res.StatusCode != 200 {
 	if res.StatusCode != 200 {
 		errBody, err := ioutil.ReadAll(res.Body)
 		errBody, err := ioutil.ReadAll(res.Body)
@@ -363,18 +415,19 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, t
 }
 }
 
 
 // Push a local image to the registry
 // Push a local image to the registry
-func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
+func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
 
 
-	logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
+	u := registry + "images/" + imgData.ID + "/json"
 
 
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
+	logrus.Debugf("[registry] Calling PUT %s", u)
+
+	req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	req.Header.Add("Content-type", "application/json")
 	req.Header.Add("Content-type", "application/json")
-	setTokenAuth(req, token)
 
 
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return fmt.Errorf("Failed to upload metadata: %s", err)
 		return fmt.Errorf("Failed to upload metadata: %s", err)
 	}
 	}
@@ -398,9 +451,11 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist
 	return nil
 	return nil
 }
 }
 
 
-func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
+func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
+
+	u := registry + "images/" + imgID + "/layer"
 
 
-	logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
+	logrus.Debugf("[registry] Calling PUT %s", u)
 
 
 	tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
 	tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
 	if err != nil {
 	if err != nil {
@@ -411,17 +466,16 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
 	h.Write([]byte{'\n'})
 	h.Write([]byte{'\n'})
 	checksumLayer := io.TeeReader(tarsumLayer, h)
 	checksumLayer := io.TeeReader(tarsumLayer, h)
 
 
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
+	req, err := http.NewRequest("PUT", u, checksumLayer)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
 	req.Header.Add("Content-Type", "application/octet-stream")
 	req.Header.Add("Content-Type", "application/octet-stream")
 	req.ContentLength = -1
 	req.ContentLength = -1
 	req.TransferEncoding = []string{"chunked"}
 	req.TransferEncoding = []string{"chunked"}
-	setTokenAuth(req, token)
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
-		return "", "", fmt.Errorf("Failed to upload layer: %s", err)
+		return "", "", fmt.Errorf("Failed to upload layer: %v", err)
 	}
 	}
 	if rc, ok := layer.(io.Closer); ok {
 	if rc, ok := layer.(io.Closer); ok {
 		if err := rc.Close(); err != nil {
 		if err := rc.Close(); err != nil {
@@ -444,19 +498,18 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
 
 
 // push a tag on the registry.
 // push a tag on the registry.
 // Remote has the format '<user>/<repo>
 // Remote has the format '<user>/<repo>
-func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
+func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
 	// "jsonify" the string
 	// "jsonify" the string
 	revision = "\"" + revision + "\""
 	revision = "\"" + revision + "\""
 	path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
 	path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
 
 
-	req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
+	req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	req.Header.Add("Content-type", "application/json")
 	req.Header.Add("Content-type", "application/json")
-	setTokenAuth(req, token)
 	req.ContentLength = int64(len(revision))
 	req.ContentLength = int64(len(revision))
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -491,7 +544,8 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
 	logrus.Debugf("[registry] PUT %s", u)
 	logrus.Debugf("[registry] PUT %s", u)
 	logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
 	logrus.Debugf("Image list pushed to index:\n%s", imgListJSON)
 	headers := map[string][]string{
 	headers := map[string][]string{
-		"Content-type":   {"application/json"},
+		"Content-type": {"application/json"},
+		// this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
 		"X-Docker-Token": {"true"},
 		"X-Docker-Token": {"true"},
 	}
 	}
 	if validate {
 	if validate {
@@ -526,9 +580,6 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
 			}
 			}
 			return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
 			return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
 		}
 		}
-		if res.Header.Get("X-Docker-Token") == "" {
-			return nil, fmt.Errorf("Index response didn't contain an access token")
-		}
 		tokens = res.Header["X-Docker-Token"]
 		tokens = res.Header["X-Docker-Token"]
 		logrus.Debugf("Auth token: %v", tokens)
 		logrus.Debugf("Auth token: %v", tokens)
 
 
@@ -539,8 +590,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
-	}
-	if validate {
+	} else {
 		if res.StatusCode != 204 {
 		if res.StatusCode != 204 {
 			errBody, err := ioutil.ReadAll(res.Body)
 			errBody, err := ioutil.ReadAll(res.Body)
 			if err != nil {
 			if err != nil {
@@ -551,22 +601,20 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate
 	}
 	}
 
 
 	return &RepositoryData{
 	return &RepositoryData{
-		Tokens:    tokens,
 		Endpoints: endpoints,
 		Endpoints: endpoints,
 	}, nil
 	}, nil
 }
 }
 
 
 func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
 func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
-	req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(body))
+	req, err := http.NewRequest("PUT", u, bytes.NewReader(body))
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
 	req.ContentLength = int64(len(body))
 	req.ContentLength = int64(len(body))
 	for k, v := range headers {
 	for k, v := range headers {
 		req.Header[k] = v
 		req.Header[k] = v
 	}
 	}
-	response, _, err := r.doRequest(req)
+	response, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -580,15 +628,7 @@ func shouldRedirect(response *http.Response) bool {
 func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
 func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
 	logrus.Debugf("Index server: %s", r.indexEndpoint)
 	logrus.Debugf("Index server: %s", r.indexEndpoint)
 	u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term)
 	u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term)
-	req, err := r.reqFactory.NewRequest("GET", u, nil)
-	if err != nil {
-		return nil, err
-	}
-	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
-		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
-	}
-	req.Header.Set("X-Docker-Token", "true")
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Get(u)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -600,6 +640,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) {
 	return result, json.NewDecoder(res.Body).Decode(result)
 	return result, json.NewDecoder(res.Body).Decode(result)
 }
 }
 
 
+// TODO(tiborvass): remove this once registry client v2 is vendored
 func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
 func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
 	password := ""
 	password := ""
 	if withPasswd {
 	if withPasswd {
@@ -611,9 +652,3 @@ func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig {
 		Email:    r.authConfig.Email,
 		Email:    r.authConfig.Email,
 	}
 	}
 }
 }
-
-func setTokenAuth(req *http.Request, token []string) {
-	if req.Header.Get("Authorization") == "" { // Don't override
-		req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
-	}
-}

+ 18 - 18
registry/session_v2.go

@@ -27,7 +27,7 @@ func getV2Builder(e *Endpoint) *v2.URLBuilder {
 func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
 func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
 	// TODO check if should use Mirror
 	// TODO check if should use Mirror
 	if index.Official {
 	if index.Official {
-		ep, err = newEndpoint(REGISTRYSERVER, true)
+		ep, err = newEndpoint(REGISTRYSERVER, true, nil)
 		if err != nil {
 		if err != nil {
 			return
 			return
 		}
 		}
@@ -38,7 +38,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
 	} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
 	} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
 		ep = r.indexEndpoint
 		ep = r.indexEndpoint
 	} else {
 	} else {
-		ep, err = NewEndpoint(index)
+		ep, err = NewEndpoint(index, nil)
 		if err != nil {
 		if err != nil {
 			return
 			return
 		}
 		}
@@ -77,14 +77,14 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
 	method := "GET"
 	method := "GET"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 
 
-	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+	req, err := http.NewRequest(method, routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return nil, "", err
 		return nil, "", err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return nil, "", err
 		return nil, "", err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return nil, "", err
 		return nil, "", err
 	}
 	}
@@ -118,14 +118,14 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Di
 	method := "HEAD"
 	method := "HEAD"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 
 
-	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+	req, err := http.NewRequest(method, routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return false, err
 		return false, err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
 	}
 	}
@@ -152,14 +152,14 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
 
 
 	method := "GET"
 	method := "GET"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
-	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+	req, err := http.NewRequest(method, routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return err
 		return err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -183,14 +183,14 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst dige
 
 
 	method := "GET"
 	method := "GET"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
-	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+	req, err := http.NewRequest(method, routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return nil, 0, err
 		return nil, 0, err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return nil, 0, err
 		return nil, 0, err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return nil, 0, err
 		return nil, 0, err
 	}
 	}
@@ -220,7 +220,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
 
 
 	method := "PUT"
 	method := "PUT"
 	logrus.Debugf("[registry] Calling %q %s", method, location)
 	logrus.Debugf("[registry] Calling %q %s", method, location)
-	req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr))
+	req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -230,7 +230,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return err
 		return err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -259,7 +259,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
 	}
 	}
 
 
 	logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
 	logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
-	req, err := r.reqFactory.NewRequest("POST", routeURL, nil)
+	req, err := http.NewRequest("POST", routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -267,7 +267,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -305,14 +305,14 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
 
 
 	method := "PUT"
 	method := "PUT"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
-	req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
+	req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -366,14 +366,14 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA
 	method := "GET"
 	method := "GET"
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 
 
-	req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+	req, err := http.NewRequest(method, routeURL, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	if err := auth.Authorize(req); err != nil {
 	if err := auth.Authorize(req); err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	res, _, err := r.doRequest(req)
+	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 3 - 5
registry/token.go

@@ -7,15 +7,13 @@ import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
-
-	"github.com/docker/docker/pkg/requestdecorator"
 )
 )
 
 
 type tokenResponse struct {
 type tokenResponse struct {
 	Token string `json:"token"`
 	Token string `json:"token"`
 }
 }
 
 
-func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) (token string, err error) {
+func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint) (token string, err error) {
 	realm, ok := params["realm"]
 	realm, ok := params["realm"]
 	if !ok {
 	if !ok {
 		return "", errors.New("no realm specified for token auth challenge")
 		return "", errors.New("no realm specified for token auth challenge")
@@ -34,7 +32,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
 		}
 		}
 	}
 	}
 
 
-	req, err := factory.NewRequest("GET", realmURL.String(), nil)
+	req, err := http.NewRequest("GET", realmURL.String(), nil)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
@@ -58,7 +56,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
 
 
 	req.URL.RawQuery = reqParams.Encode()
 	req.URL.RawQuery = reqParams.Encode()
 
 
-	resp, err := client.Do(req)
+	resp, err := registryEndpoint.client.Do(req)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}