Переглянути джерело

Change APIEndpoint to contain the URL in a parsed format

This allows easier URL handling in code that uses APIEndpoint.
If we continued to store the URL unparsed, it would require redundant
parsing whenver we want to extract information from it. Also, parsing
the URL earlier should give improve validation.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann 9 роки тому
батько
коміт
79db131a35

+ 4 - 11
distribution/pull.go

@@ -2,7 +2,6 @@ package distribution
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"net/url"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -122,14 +121,8 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 			continue
 			continue
 		}
 		}
 
 
-		parsedURL, urlErr := url.Parse(endpoint.URL)
-		if urlErr != nil {
-			logrus.Errorf("Failed to parse endpoint URL %s", endpoint.URL)
-			continue
-		}
-
-		if parsedURL.Scheme != "https" {
-			if _, confirmedTLS := confirmedTLSRegistries[parsedURL.Host]; confirmedTLS {
+		if endpoint.URL.Scheme != "https" {
+			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
 				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
 				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
 				continue
 				continue
 			}
 			}
@@ -152,8 +145,8 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 				if fallbackErr, ok := err.(fallbackError); ok {
 				if fallbackErr, ok := err.(fallbackError); ok {
 					fallback = true
 					fallback = true
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
-					if fallbackErr.transportOK && parsedURL.Scheme == "https" {
-						confirmedTLSRegistries[parsedURL.Host] = struct{}{}
+					if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
+						confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
 					}
 					}
 					err = fallbackErr.err
 					err = fallbackErr.err
 				}
 				}

+ 4 - 11
distribution/push.go

@@ -5,7 +5,6 @@ import (
 	"compress/gzip"
 	"compress/gzip"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
-	"net/url"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/metadata"
@@ -133,14 +132,8 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 			continue
 			continue
 		}
 		}
 
 
-		parsedURL, urlErr := url.Parse(endpoint.URL)
-		if urlErr != nil {
-			logrus.Errorf("Failed to parse endpoint URL %s", endpoint.URL)
-			continue
-		}
-
-		if parsedURL.Scheme != "https" {
-			if _, confirmedTLS := confirmedTLSRegistries[parsedURL.Host]; confirmedTLS {
+		if endpoint.URL.Scheme != "https" {
+			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
 				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
 				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
 				continue
 				continue
 			}
 			}
@@ -161,8 +154,8 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 			default:
 			default:
 				if fallbackErr, ok := err.(fallbackError); ok {
 				if fallbackErr, ok := err.(fallbackError); ok {
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
-					if fallbackErr.transportOK && parsedURL.Scheme == "https" {
-						confirmedTLSRegistries[parsedURL.Host] = struct{}{}
+					if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
+						confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
 					}
 					}
 					err = fallbackErr.err
 					err = fallbackErr.err
 					lastErr = err
 					lastErr = err

+ 2 - 2
distribution/registry.go

@@ -57,7 +57,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 		Transport: authTransport,
 		Transport: authTransport,
 		Timeout:   15 * time.Second,
 		Timeout:   15 * time.Second,
 	}
 	}
-	endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
+	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
 	req, err := http.NewRequest("GET", endpointStr, nil)
 	req, err := http.NewRequest("GET", endpointStr, nil)
 	if err != nil {
 	if err != nil {
 		return nil, false, fallbackError{err: err}
 		return nil, false, fallbackError{err: err}
@@ -118,7 +118,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 		}
 		}
 	}
 	}
 
 
-	repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL, tr)
+	repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL.String(), tr)
 	if err != nil {
 	if err != nil {
 		err = fallbackError{
 		err = fallbackError{
 			err:         err,
 			err:         err,

+ 7 - 1
distribution/registry_unit_test.go

@@ -3,6 +3,7 @@ package distribution
 import (
 import (
 	"net/http"
 	"net/http"
 	"net/http/httptest"
 	"net/http/httptest"
+	"net/url"
 	"os"
 	"os"
 	"strings"
 	"strings"
 	"testing"
 	"testing"
@@ -43,9 +44,14 @@ func testTokenPassThru(t *testing.T, ts *httptest.Server) {
 	}
 	}
 	defer os.RemoveAll(tmp)
 	defer os.RemoveAll(tmp)
 
 
+	uri, err := url.Parse(ts.URL)
+	if err != nil {
+		t.Fatalf("could not parse url from test server: %v", err)
+	}
+
 	endpoint := registry.APIEndpoint{
 	endpoint := registry.APIEndpoint{
 		Mirror:       false,
 		Mirror:       false,
-		URL:          ts.URL,
+		URL:          uri,
 		Version:      2,
 		Version:      2,
 		Official:     false,
 		Official:     false,
 		TrimHostname: false,
 		TrimHostname: false,

+ 2 - 2
registry/config.go

@@ -19,7 +19,7 @@ type Options struct {
 	InsecureRegistries opts.ListOpts
 	InsecureRegistries opts.ListOpts
 }
 }
 
 
-const (
+var (
 	// DefaultNamespace is the default namespace
 	// DefaultNamespace is the default namespace
 	DefaultNamespace = "docker.io"
 	DefaultNamespace = "docker.io"
 	// DefaultRegistryVersionHeader is the name of the default HTTP header
 	// DefaultRegistryVersionHeader is the name of the default HTTP header
@@ -27,7 +27,7 @@ const (
 	DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version"
 	DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version"
 
 
 	// IndexServer is the v1 registry server used for user auth + account creation
 	// IndexServer is the v1 registry server used for user auth + account creation
-	IndexServer = DefaultV1Registry + "/v1/"
+	IndexServer = DefaultV1Registry.String() + "/v1/"
 	// IndexName is the name of the index
 	// IndexName is the name of the index
 	IndexName = "docker.io"
 	IndexName = "docker.io"
 
 

+ 13 - 3
registry/config_unix.go

@@ -2,12 +2,22 @@
 
 
 package registry
 package registry
 
 
-const (
+import (
+	"net/url"
+)
+
+var (
 	// DefaultV1Registry is the URI of the default v1 registry
 	// DefaultV1Registry is the URI of the default v1 registry
-	DefaultV1Registry = "https://index.docker.io"
+	DefaultV1Registry = &url.URL{
+		Scheme: "https",
+		Host:   "index.docker.io",
+	}
 
 
 	// DefaultV2Registry is the URI of the default v2 registry
 	// DefaultV2Registry is the URI of the default v2 registry
-	DefaultV2Registry = "https://registry-1.docker.io"
+	DefaultV2Registry = &url.URL{
+		Scheme: "https",
+		Host:   "registry-1.docker.io",
+	}
 )
 )
 
 
 var (
 var (

+ 10 - 3
registry/config_windows.go

@@ -1,21 +1,28 @@
 package registry
 package registry
 
 
 import (
 import (
+	"net/url"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
 )
 )
 
 
-const (
+var (
 	// DefaultV1Registry is the URI of the default v1 registry
 	// DefaultV1Registry is the URI of the default v1 registry
-	DefaultV1Registry = "https://registry-win-tp3.docker.io"
+	DefaultV1Registry = &url.URL{
+		Scheme: "https",
+		Host:   "registry-win-tp3.docker.io",
+	}
 
 
 	// DefaultV2Registry is the URI of the default (official) v2 registry.
 	// DefaultV2Registry is the URI of the default (official) v2 registry.
 	// This is the windows-specific endpoint.
 	// This is the windows-specific endpoint.
 	//
 	//
 	// Currently it is a TEMPORARY link that allows Microsoft to continue
 	// Currently it is a TEMPORARY link that allows Microsoft to continue
 	// development of Docker Engine for Windows.
 	// development of Docker Engine for Windows.
-	DefaultV2Registry = "https://registry-win-tp3.docker.io"
+	DefaultV2Registry = &url.URL{
+		Scheme: "https",
+		Host:   "registry-win-tp3.docker.io",
+	}
 )
 )
 
 
 // CertsDir is the directory where certificates are stored
 // CertsDir is the directory where certificates are stored

+ 27 - 14
registry/endpoint.go

@@ -50,10 +50,12 @@ func NewEndpoint(index *registrytypes.IndexInfo, userAgent string, metaHeaders h
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	endpoint, err := newEndpoint(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders)
+
+	endpoint, err := newEndpointFromStr(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+
 	if v != APIVersionUnknown {
 	if v != APIVersionUnknown {
 		endpoint.Version = v
 		endpoint.Version = v
 	}
 	}
@@ -91,28 +93,39 @@ func validateEndpoint(endpoint *Endpoint) error {
 	return nil
 	return nil
 }
 }
 
 
-func newEndpoint(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*Endpoint, error) {
-	var (
-		endpoint       = new(Endpoint)
-		trimmedAddress string
-		err            error
-	)
+func newEndpoint(address url.URL, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*Endpoint, error) {
+	endpoint := &Endpoint{
+		IsSecure: (tlsConfig == nil || !tlsConfig.InsecureSkipVerify),
+		URL:      new(url.URL),
+		Version:  APIVersionUnknown,
+	}
 
 
-	if !strings.HasPrefix(address, "http") {
+	*endpoint.URL = address
+
+	// TODO(tiborvass): make sure a ConnectTimeout transport is used
+	tr := NewTransport(tlsConfig)
+	endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...))
+	return endpoint, nil
+}
+
+func newEndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*Endpoint, error) {
+	if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
 		address = "https://" + address
 		address = "https://" + address
 	}
 	}
 
 
-	endpoint.IsSecure = (tlsConfig == nil || !tlsConfig.InsecureSkipVerify)
+	trimmedAddress, detectedVersion := scanForAPIVersion(address)
 
 
-	trimmedAddress, endpoint.Version = scanForAPIVersion(address)
+	uri, err := url.Parse(trimmedAddress)
+	if err != nil {
+		return nil, err
+	}
 
 
-	if endpoint.URL, err = url.Parse(trimmedAddress); err != nil {
+	endpoint, err := newEndpoint(*uri, tlsConfig, userAgent, metaHeaders)
+	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	// TODO(tiborvass): make sure a ConnectTimeout transport is used
-	tr := NewTransport(tlsConfig)
-	endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(userAgent, metaHeaders)...))
+	endpoint.Version = detectedVersion
 	return endpoint, nil
 	return endpoint, nil
 }
 }
 
 

+ 1 - 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, nil, "", nil)
+		e, err := newEndpointFromStr(td.str, nil, "", nil)
 		if err != nil {
 		if err != nil {
 			t.Errorf("%q: %s", td.str, err)
 			t.Errorf("%q: %s", td.str, err)
 		}
 		}

+ 1 - 1
registry/registry_test.go

@@ -673,7 +673,7 @@ func TestNewIndexInfo(t *testing.T) {
 func TestMirrorEndpointLookup(t *testing.T) {
 func TestMirrorEndpointLookup(t *testing.T) {
 	containsMirror := func(endpoints []APIEndpoint) bool {
 	containsMirror := func(endpoints []APIEndpoint) bool {
 		for _, pe := range endpoints {
 		for _, pe := range endpoints {
-			if pe.URL == "my.mirror" {
+			if pe.URL.Host == "my.mirror" {
 				return true
 				return true
 			}
 			}
 		}
 		}

+ 3 - 7
registry/service.go

@@ -121,7 +121,7 @@ func (s *Service) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
 // APIEndpoint represents a remote API endpoint
 // APIEndpoint represents a remote API endpoint
 type APIEndpoint struct {
 type APIEndpoint struct {
 	Mirror       bool
 	Mirror       bool
-	URL          string
+	URL          *url.URL
 	Version      APIVersion
 	Version      APIVersion
 	Official     bool
 	Official     bool
 	TrimHostname bool
 	TrimHostname bool
@@ -130,7 +130,7 @@ type APIEndpoint struct {
 
 
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*Endpoint, error) {
 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*Endpoint, error) {
-	return newEndpoint(e.URL, e.TLSConfig, userAgent, metaHeaders)
+	return newEndpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
 }
 }
 
 
 // TLSConfig constructs a client TLS configuration based on server defaults
 // TLSConfig constructs a client TLS configuration based on server defaults
@@ -138,11 +138,7 @@ func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
 	return newTLSConfig(hostname, isSecureIndex(s.Config, hostname))
 	return newTLSConfig(hostname, isSecureIndex(s.Config, hostname))
 }
 }
 
 
-func (s *Service) tlsConfigForMirror(mirror string) (*tls.Config, error) {
-	mirrorURL, err := url.Parse(mirror)
-	if err != nil {
-		return nil, err
-	}
+func (s *Service) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
 	return s.TLSConfig(mirrorURL.Host)
 	return s.TLSConfig(mirrorURL.Host)
 }
 }
 
 

+ 9 - 2
registry/service_v1.go

@@ -2,6 +2,7 @@ package registry
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"net/url"
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
@@ -36,7 +37,10 @@ func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEn
 
 
 	endpoints = []APIEndpoint{
 	endpoints = []APIEndpoint{
 		{
 		{
-			URL:          "https://" + hostname,
+			URL: &url.URL{
+				Scheme: "https",
+				Host:   hostname,
+			},
 			Version:      APIVersion1,
 			Version:      APIVersion1,
 			TrimHostname: true,
 			TrimHostname: true,
 			TLSConfig:    tlsConfig,
 			TLSConfig:    tlsConfig,
@@ -45,7 +49,10 @@ func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEn
 
 
 	if tlsConfig.InsecureSkipVerify {
 	if tlsConfig.InsecureSkipVerify {
 		endpoints = append(endpoints, APIEndpoint{ // or this
 		endpoints = append(endpoints, APIEndpoint{ // or this
-			URL:          "http://" + hostname,
+			URL: &url.URL{
+				Scheme: "http",
+				Host:   hostname,
+			},
 			Version:      APIVersion1,
 			Version:      APIVersion1,
 			TrimHostname: true,
 			TrimHostname: true,
 			// used to check if supposed to be secure via InsecureSkipVerify
 			// used to check if supposed to be secure via InsecureSkipVerify

+ 18 - 4
registry/service_v2.go

@@ -2,6 +2,7 @@ package registry
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"net/url"
 	"strings"
 	"strings"
 
 
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
@@ -15,12 +16,19 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
 	if strings.HasPrefix(nameString, DefaultNamespace+"/") {
 	if strings.HasPrefix(nameString, DefaultNamespace+"/") {
 		// v2 mirrors
 		// v2 mirrors
 		for _, mirror := range s.Config.Mirrors {
 		for _, mirror := range s.Config.Mirrors {
-			mirrorTLSConfig, err := s.tlsConfigForMirror(mirror)
+			if !strings.HasPrefix(mirror, "http://") && !strings.HasPrefix(mirror, "https://") {
+				mirror = "https://" + mirror
+			}
+			mirrorURL, err := url.Parse(mirror)
+			if err != nil {
+				return nil, err
+			}
+			mirrorTLSConfig, err := s.tlsConfigForMirror(mirrorURL)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
 			endpoints = append(endpoints, APIEndpoint{
 			endpoints = append(endpoints, APIEndpoint{
-				URL: mirror,
+				URL: mirrorURL,
 				// guess mirrors are v2
 				// guess mirrors are v2
 				Version:      APIVersion2,
 				Version:      APIVersion2,
 				Mirror:       true,
 				Mirror:       true,
@@ -53,7 +61,10 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
 
 
 	endpoints = []APIEndpoint{
 	endpoints = []APIEndpoint{
 		{
 		{
-			URL:          "https://" + hostname,
+			URL: &url.URL{
+				Scheme: "https",
+				Host:   hostname,
+			},
 			Version:      APIVersion2,
 			Version:      APIVersion2,
 			TrimHostname: true,
 			TrimHostname: true,
 			TLSConfig:    tlsConfig,
 			TLSConfig:    tlsConfig,
@@ -62,7 +73,10 @@ func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEn
 
 
 	if tlsConfig.InsecureSkipVerify {
 	if tlsConfig.InsecureSkipVerify {
 		endpoints = append(endpoints, APIEndpoint{
 		endpoints = append(endpoints, APIEndpoint{
-			URL:          "http://" + hostname,
+			URL: &url.URL{
+				Scheme: "http",
+				Host:   hostname,
+			},
 			Version:      APIVersion2,
 			Version:      APIVersion2,
 			TrimHostname: true,
 			TrimHostname: true,
 			// used to check if supposed to be secure via InsecureSkipVerify
 			// used to check if supposed to be secure via InsecureSkipVerify