浏览代码

registry: Refactor requestfactory to use http.RoundTrippers

This patch removes the need for requestFactories and decorators
by implementing http.RoundTripper transports instead.

It refactors some challenging-to-read code.

NewSession now takes an *http.Client that can already have a
custom Transport, it will add its own auth transport by wrapping
it.

The idea is that callers of http.Client should not bother
setting custom headers for every handler but instead it should
be transparent to the callers of a same context.

This patch is needed for future refactorings of registry,
namely refactoring of the v1 client code.

Signed-off-by: Tibor Vass <tibor@docker.com>
Tibor Vass 10 年之前
父节点
当前提交
a01cc3ca77
共有 12 个文件被更改,包括 394 次插入363 次删除
  1. 11 5
      graph/pull.go
  2. 11 6
      graph/push.go
  3. 2 2
      pkg/requestdecorator/requestdecorator.go
  4. 15 17
      registry/auth.go
  5. 15 26
      registry/endpoint.go
  6. 0 30
      registry/httpfactory.go
  7. 151 71
      registry/registry.go
  8. 27 39
      registry/registry_test.go
  9. 3 2
      registry/service.go
  10. 141 145
      registry/session.go
  11. 16 16
      registry/session_v2.go
  12. 2 4
      registry/token.go

+ 11 - 5
graph/pull.go

@@ -60,7 +60,13 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf
 		return err
 		return err
 	}
 	}
 
 
-	r, err := registry.NewSession(imagePullConfig.AuthConfig, registry.HTTPRequestFactory(imagePullConfig.MetaHeaders), endpoint, true)
+	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+	tr := &registry.DockerHeaders{
+		registry.NewTransport(registry.ReceiveTimeout, endpoint.IsSecure),
+		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 +115,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 +246,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 +275,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 +303,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
 				}
 				}

+ 11 - 6
graph/push.go

@@ -141,7 +141,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 +199,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 +258,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 +284,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
 	}
 	}
 
 
@@ -514,7 +514,12 @@ func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) erro
 		return err
 		return err
 	}
 	}
 
 
-	r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false)
+	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+	tr := &registry.DockerHeaders{
+		registry.NewTransport(registry.NoTimeout, endpoint.IsSecure),
+		imagePushConfig.MetaHeaders,
+	}
+	r, err := registry.NewSession(client, imagePushConfig.AuthConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 2 - 2
pkg/requestdecorator/requestdecorator.go

@@ -47,7 +47,7 @@ func (vi *UAVersionInfo) isValid() bool {
 // "product/version", where the "product" is get from the name field, while
 // "product/version", where the "product" is get from the name field, while
 // version is get from the version field. Several pieces of verson information
 // version is get from the version field. Several pieces of verson information
 // will be concatinated and separated by space.
 // will be concatinated and separated by space.
-func appendVersions(base string, versions ...UAVersionInfo) string {
+func AppendVersions(base string, versions ...UAVersionInfo) string {
 	if len(versions) == 0 {
 	if len(versions) == 0 {
 		return base
 		return base
 	}
 	}
@@ -87,7 +87,7 @@ func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, er
 		return req, ErrNilRequest
 		return req, ErrNilRequest
 	}
 	}
 
 
-	userAgent := appendVersions(req.UserAgent(), h.Versions...)
+	userAgent := AppendVersions(req.UserAgent(), h.Versions...)
 	if len(userAgent) > 0 {
 	if len(userAgent) > 0 {
 		req.Header.Set("User-Agent", userAgent)
 		req.Header.Set("User-Agent", userAgent)
 	}
 	}

+ 15 - 17
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 {
@@ -46,7 +45,6 @@ func (auth *RequestAuthorization) getToken() (string, error) {
 	}
 	}
 
 
 	client := auth.registryEndpoint.HTTPClient()
 	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) {
@@ -59,7 +57,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, client)
 			if err != nil {
 			if err != nil {
 				return "", err
 				return "", err
 			}
 			}
@@ -92,16 +90,16 @@ 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
@@ -151,7 +149,7 @@ 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 := client.Do(req)
 			if err != nil {
 			if err != nil {
@@ -180,7 +178,7 @@ 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 := client.Do(req)
 		if err != nil {
 		if err != nil {
@@ -214,7 +212,7 @@ 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
@@ -227,9 +225,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, client)
 		case "bearer":
 		case "bearer":
-			err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+			err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client)
 		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,8 +245,8 @@ 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, client *http.Client) error {
+	req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -268,13 +266,13 @@ 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, client *http.Client) error {
+	token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client)
 	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
 	}
 	}

+ 15 - 26
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,6 @@ 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"
 )
 )
 
 
 // for mocking in unit tests
 // for mocking in unit tests
@@ -109,6 +107,7 @@ func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
 
 
 // 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 +134,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 +160,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 +169,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.HTTPClient().Do(req)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{Standalone: false}, err
 		return RegistryInfo{Standalone: false}, err
 	}
 	}
@@ -216,15 +214,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.HTTPClient().Do(req)
 	if err != nil {
 	if err != nil {
 		return RegistryInfo{}, err
 		return RegistryInfo{}, err
 	}
 	}
@@ -265,18 +263,9 @@ HeaderLoop:
 }
 }
 
 
 func (e *Endpoint) HTTPClient() *http.Client {
 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,
+	if e.client == nil {
+		tr := NewTransport(ConnectTimeout, e.IsSecure)
+		e.client = HTTPClient(tr)
 	}
 	}
+	return e.client
 }
 }

+ 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
-}

+ 151 - 71
registry/registry.go

@@ -8,12 +8,17 @@ 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/requestdecorator"
 	"github.com/docker/docker/pkg/timeoutconn"
 	"github.com/docker/docker/pkg/timeoutconn"
 )
 )
 
 
@@ -31,66 +36,23 @@ 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,
-	}
-
-	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,
-	}
+type httpsTransport struct {
+	*http.Transport
 }
 }
 
 
-func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
+// 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.
+// TODO: improve things
+func (tr *httpsTransport) RoundTrip(req *http.Request) (*http.Response, 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 +66,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, err
 		}
 		}
 
 
 		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 nil, 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 nil, 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 nil, err
 				}
 				}
 				certs = append(certs, cert)
 				certs = append(certs, cert)
 			}
 			}
@@ -137,24 +99,142 @@ 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 nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
 				}
 				}
 			}
 			}
 		}
 		}
+		if tr.Transport.TLSClientConfig == nil {
+			tr.Transport.TLSClientConfig = &tls.Config{
+				// Avoid fallback to SSL protocols < TLS1.0
+				MinVersion: tls.VersionTLS10,
+			}
+		}
+		tr.Transport.TLSClientConfig.RootCAs = roots
+		tr.Transport.TLSClientConfig.Certificates = certs
+	}
+	return tr.Transport.RoundTrip(req)
+}
+
+func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
+	tlsConfig := tls.Config{
+		// Avoid fallback to SSL protocols < TLS1.0
+		MinVersion:         tls.VersionTLS10,
+		InsecureSkipVerify: !secure,
+	}
+
+	transport := &http.Transport{
+		DisableKeepAlives: true,
+		Proxy:             http.ProxyFromEnvironment,
+		TLSClientConfig:   &tlsConfig,
 	}
 	}
 
 
-	if len(certs) == 0 {
-		client := newClient(jar, pool, nil, timeout, secure)
-		res, err := client.Do(req)
-		if err != nil {
-			return nil, nil, err
+	switch timeout {
+	case ConnectTimeout:
+		transport.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:
+		transport.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 &httpsTransport{transport}
+	}
+
+	return transport
+}
+
+type DockerHeaders struct {
+	http.RoundTripper
+	Headers http.Header
+}
+
+// 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
+}
+
+func (tr *DockerHeaders) RoundTrip(req *http.Request) (*http.Response, error) {
+	req = cloneRequest(req)
+	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))
+
+	userAgent := requestdecorator.AppendVersions(req.UserAgent(), httpVersion...)
+
+	req.Header.Set("User-Agent", userAgent)
+
+	for k, v := range tr.Headers {
+		req.Header[k] = v
+	}
+	return tr.RoundTripper.RoundTrip(req)
+}
+
+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 {

+ 27 - 39
registry/registry_test.go

@@ -8,7 +8,6 @@ import (
 	"testing"
 	"testing"
 
 
 	"github.com/docker/docker/cliconfig"
 	"github.com/docker/docker/cliconfig"
-	"github.com/docker/docker/pkg/requestdecorator"
 )
 )
 
 
 var (
 var (
@@ -26,38 +25,27 @@ func spawnTestRegistrySession(t *testing.T) *Session {
 	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 = &DockerHeaders{&authTransport{RoundTripper: tr, AuthConfig: authConfig}, 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)
@@ -170,7 +158,7 @@ func TestEndpoint(t *testing.T) {
 
 
 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 +170,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 +188,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 +196,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 +204,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 +212,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 +253,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 +262,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 +682,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)
 	}
 	}

+ 3 - 2
registry/service.go

@@ -32,7 +32,7 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
 		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 +42,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()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	r, err := NewSession(authConfig, HTTPRequestFactory(headers), endpoint, true)
+	r, err := NewSession(endpoint.HTTPClient(), authConfig, endpoint)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 141 - 145
registry/session.go

@@ -3,6 +3,7 @@ package registry
 import (
 import (
 	"bytes"
 	"bytes"
 	"crypto/sha256"
 	"crypto/sha256"
+	"errors"
 	// this is required for some certificates
 	// this is required for some certificates
 	_ "crypto/sha512"
 	_ "crypto/sha512"
 	"encoding/hex"
 	"encoding/hex"
@@ -20,64 +21,105 @@ 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"
 )
 )
 
 
 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,
-	}
+// 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.
+type authTransport struct {
+	http.RoundTripper
+	*cliconfig.AuthConfig
+
+	alwaysSetBasicAuth bool
+	token              []string
+}
 
 
-	if timeout {
-		r.timeout = ReceiveTimeout
+func (tr *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	req = cloneRequest(req)
+
+	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 {
 		return nil, err
 		return nil, err
 	}
 	}
+	if askedForToken && len(resp.Header["X-Docker-Token"]) > 0 {
+		tr.token = resp.Header["X-Docker-Token"]
+	}
+	return resp, nil
+}
+
+// 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{RoundTripper: client.Transport, AuthConfig: authConfig, alwaysSetBasicAuth: 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 +131,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 +154,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 +174,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 +222,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 +236,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 +284,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 +310,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 +335,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 {
+
+	u := registry + "images/" + imgData.ID + "/checksum"
 
 
-	logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
+	logrus.Debugf("[registry] Calling PUT %s", u)
 
 
-	req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
+	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 +376,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 +412,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 +427,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 +459,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 +505,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 +541,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 +551,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 +562,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 +589,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 +601,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 +613,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, ","))
-	}
-}

+ 16 - 16
registry/session_v2.go

@@ -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
 	}
 	}

+ 2 - 4
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, client *http.Client) (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
 	}
 	}