Update graph to use vendored distribution client for the v2 codepath
Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan) Signed-off-by: Tibor Vass <tibor@docker.com>master
							parent
							
								
									950cf586c8
								
							
						
					
					
						commit
						7fed379d95
					
				| 
						 | 
					@ -125,7 +125,7 @@ func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (stri
 | 
				
			||||||
		return "", fmt.Errorf("Server Error: Server Address not set.")
 | 
							return "", fmt.Errorf("Server Error: Server Address not set.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	loginAgainstOfficialIndex := serverAddress == IndexServerAddress()
 | 
						loginAgainstOfficialIndex := serverAddress == INDEXSERVER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// to avoid sending the server address to the server it should be removed before being marshalled
 | 
						// to avoid sending the server address to the server it should be removed before being marshalled
 | 
				
			||||||
	authCopy := *authConfig
 | 
						authCopy := *authConfig
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ func setupTempConfigFile() (*cliconfig.ConfigFile, error) {
 | 
				
			||||||
	root = filepath.Join(root, cliconfig.CONFIGFILE)
 | 
						root = filepath.Join(root, cliconfig.CONFIGFILE)
 | 
				
			||||||
	configFile := cliconfig.NewConfigFile(root)
 | 
						configFile := cliconfig.NewConfigFile(root)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, registry := range []string{"testIndex", IndexServerAddress()} {
 | 
						for _, registry := range []string{"testIndex", INDEXSERVER} {
 | 
				
			||||||
		configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
 | 
							configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
 | 
				
			||||||
			Username: "docker-user",
 | 
								Username: "docker-user",
 | 
				
			||||||
			Password: "docker-pass",
 | 
								Password: "docker-pass",
 | 
				
			||||||
| 
						 | 
					@ -82,7 +82,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer os.RemoveAll(configFile.Filename())
 | 
						defer os.RemoveAll(configFile.Filename())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	indexConfig := configFile.AuthConfigs[IndexServerAddress()]
 | 
						indexConfig := configFile.AuthConfigs[INDEXSERVER]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	officialIndex := &IndexInfo{
 | 
						officialIndex := &IndexInfo{
 | 
				
			||||||
		Official: true,
 | 
							Official: true,
 | 
				
			||||||
| 
						 | 
					@ -92,10 +92,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resolved := ResolveAuthConfig(configFile, officialIndex)
 | 
						resolved := ResolveAuthConfig(configFile, officialIndex)
 | 
				
			||||||
	assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
 | 
						assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return INDEXSERVER")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	resolved = ResolveAuthConfig(configFile, privateIndex)
 | 
						resolved = ResolveAuthConfig(configFile, privateIndex)
 | 
				
			||||||
	assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
 | 
						assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return INDEXSERVER")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestResolveAuthConfigFullURL(t *testing.T) {
 | 
					func TestResolveAuthConfigFullURL(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -120,7 +120,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
 | 
				
			||||||
		Password: "baz-pass",
 | 
							Password: "baz-pass",
 | 
				
			||||||
		Email:    "baz@example.com",
 | 
							Email:    "baz@example.com",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	configFile.AuthConfigs[IndexServerAddress()] = officialAuth
 | 
						configFile.AuthConfigs[INDEXSERVER] = officialAuth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expectedAuths := map[string]cliconfig.AuthConfig{
 | 
						expectedAuths := map[string]cliconfig.AuthConfig{
 | 
				
			||||||
		"registry.example.com": registryAuth,
 | 
							"registry.example.com": registryAuth,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,9 +21,16 @@ type Options struct {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
 | 
						DEFAULT_NAMESPACE               = "docker.io"
 | 
				
			||||||
 | 
						DEFAULT_V2_REGISTRY             = "https://registry-1.docker.io"
 | 
				
			||||||
 | 
						DEFAULT_REGISTRY_VERSION_HEADER = "Docker-Distribution-Api-Version"
 | 
				
			||||||
 | 
						DEFAULT_V1_REGISTRY             = "https://index.docker.io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CERTS_DIR = "/etc/docker/certs.d"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Only used for user auth + account creation
 | 
						// Only used for user auth + account creation
 | 
				
			||||||
	INDEXSERVER    = "https://index.docker.io/v1/"
 | 
						REGISTRYSERVER = DEFAULT_V2_REGISTRY
 | 
				
			||||||
	REGISTRYSERVER = "https://registry-1.docker.io/v2/"
 | 
						INDEXSERVER    = DEFAULT_V1_REGISTRY + "/v1/"
 | 
				
			||||||
	INDEXNAME      = "docker.io"
 | 
						INDEXNAME      = "docker.io"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
 | 
						// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
 | 
				
			||||||
| 
						 | 
					@ -34,14 +41,6 @@ var (
 | 
				
			||||||
	emptyServiceConfig       = NewServiceConfig(nil)
 | 
						emptyServiceConfig       = NewServiceConfig(nil)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func IndexServerAddress() string {
 | 
					 | 
				
			||||||
	return INDEXSERVER
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func IndexServerName() string {
 | 
					 | 
				
			||||||
	return INDEXNAME
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// InstallFlags adds command-line options to the top-level flag parser for
 | 
					// InstallFlags adds command-line options to the top-level flag parser for
 | 
				
			||||||
// the current process.
 | 
					// the current process.
 | 
				
			||||||
func (options *Options) InstallFlags() {
 | 
					func (options *Options) InstallFlags() {
 | 
				
			||||||
| 
						 | 
					@ -72,6 +71,7 @@ func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
 | 
				
			||||||
type ServiceConfig struct {
 | 
					type ServiceConfig struct {
 | 
				
			||||||
	InsecureRegistryCIDRs []*netIPNet           `json:"InsecureRegistryCIDRs"`
 | 
						InsecureRegistryCIDRs []*netIPNet           `json:"InsecureRegistryCIDRs"`
 | 
				
			||||||
	IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
 | 
						IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
 | 
				
			||||||
 | 
						Mirrors               []string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewServiceConfig returns a new instance of ServiceConfig
 | 
					// NewServiceConfig returns a new instance of ServiceConfig
 | 
				
			||||||
| 
						 | 
					@ -93,6 +93,9 @@ func NewServiceConfig(options *Options) *ServiceConfig {
 | 
				
			||||||
	config := &ServiceConfig{
 | 
						config := &ServiceConfig{
 | 
				
			||||||
		InsecureRegistryCIDRs: make([]*netIPNet, 0),
 | 
							InsecureRegistryCIDRs: make([]*netIPNet, 0),
 | 
				
			||||||
		IndexConfigs:          make(map[string]*IndexInfo, 0),
 | 
							IndexConfigs:          make(map[string]*IndexInfo, 0),
 | 
				
			||||||
 | 
							// Hack: Bypass setting the mirrors to IndexConfigs since they are going away
 | 
				
			||||||
 | 
							// and Mirrors are only for the official registry anyways.
 | 
				
			||||||
 | 
							Mirrors: options.Mirrors.GetAll(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Split --insecure-registry into CIDR and registry-specific settings.
 | 
						// Split --insecure-registry into CIDR and registry-specific settings.
 | 
				
			||||||
	for _, r := range options.InsecureRegistries.GetAll() {
 | 
						for _, r := range options.InsecureRegistries.GetAll() {
 | 
				
			||||||
| 
						 | 
					@ -113,9 +116,9 @@ func NewServiceConfig(options *Options) *ServiceConfig {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Configure public registry.
 | 
						// Configure public registry.
 | 
				
			||||||
	config.IndexConfigs[IndexServerName()] = &IndexInfo{
 | 
						config.IndexConfigs[INDEXNAME] = &IndexInfo{
 | 
				
			||||||
		Name:     IndexServerName(),
 | 
							Name:     INDEXNAME,
 | 
				
			||||||
		Mirrors:  options.Mirrors.GetAll(),
 | 
							Mirrors:  config.Mirrors,
 | 
				
			||||||
		Secure:   true,
 | 
							Secure:   true,
 | 
				
			||||||
		Official: true,
 | 
							Official: true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -193,8 +196,8 @@ func ValidateMirror(val string) (string, error) {
 | 
				
			||||||
// ValidateIndexName validates an index name.
 | 
					// ValidateIndexName validates an index name.
 | 
				
			||||||
func ValidateIndexName(val string) (string, error) {
 | 
					func ValidateIndexName(val string) (string, error) {
 | 
				
			||||||
	// 'index.docker.io' => 'docker.io'
 | 
						// 'index.docker.io' => 'docker.io'
 | 
				
			||||||
	if val == "index."+IndexServerName() {
 | 
						if val == "index."+INDEXNAME {
 | 
				
			||||||
		val = IndexServerName()
 | 
							val = INDEXNAME
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
 | 
						if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
 | 
				
			||||||
		return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
 | 
							return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
 | 
				
			||||||
| 
						 | 
					@ -264,7 +267,7 @@ func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error)
 | 
				
			||||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
 | 
					// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
 | 
				
			||||||
func (index *IndexInfo) GetAuthConfigKey() string {
 | 
					func (index *IndexInfo) GetAuthConfigKey() string {
 | 
				
			||||||
	if index.Official {
 | 
						if index.Official {
 | 
				
			||||||
		return IndexServerAddress()
 | 
							return INDEXSERVER
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return index.Name
 | 
						return index.Name
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -277,7 +280,7 @@ func splitReposName(reposName string) (string, string) {
 | 
				
			||||||
		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
 | 
							!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
 | 
				
			||||||
		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
 | 
							// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
 | 
				
			||||||
		// 'docker.io'
 | 
							// 'docker.io'
 | 
				
			||||||
		indexName = IndexServerName()
 | 
							indexName = INDEXNAME
 | 
				
			||||||
		remoteName = reposName
 | 
							remoteName = reposName
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		indexName = nameParts[0]
 | 
							indexName = nameParts[0]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
package registry
 | 
					package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
| 
						 | 
					@ -12,6 +13,7 @@ import (
 | 
				
			||||||
	"github.com/Sirupsen/logrus"
 | 
						"github.com/Sirupsen/logrus"
 | 
				
			||||||
	"github.com/docker/distribution/registry/api/v2"
 | 
						"github.com/docker/distribution/registry/api/v2"
 | 
				
			||||||
	"github.com/docker/distribution/registry/client/transport"
 | 
						"github.com/docker/distribution/registry/client/transport"
 | 
				
			||||||
 | 
						"github.com/docker/docker/pkg/tlsconfig"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// for mocking in unit tests
 | 
					// for mocking in unit tests
 | 
				
			||||||
| 
						 | 
					@ -44,7 +46,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
 | 
				
			||||||
// NewEndpoint parses the given address to return a registry endpoint.
 | 
					// NewEndpoint parses the given address to return a registry endpoint.
 | 
				
			||||||
func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
 | 
					func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
 | 
				
			||||||
	// *TODO: Allow per-registry configuration of endpoints.
 | 
						// *TODO: Allow per-registry configuration of endpoints.
 | 
				
			||||||
	endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, metaHeaders)
 | 
						tlsConfig := tlsconfig.ServerDefault
 | 
				
			||||||
 | 
						tlsConfig.InsecureSkipVerify = !index.Secure
 | 
				
			||||||
 | 
						endpoint, err := newEndpoint(index.GetAuthConfigKey(), &tlsConfig, metaHeaders)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -82,7 +86,7 @@ func validateEndpoint(endpoint *Endpoint) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoint, error) {
 | 
					func newEndpoint(address string, tlsConfig *tls.Config, metaHeaders http.Header) (*Endpoint, error) {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		endpoint       = new(Endpoint)
 | 
							endpoint       = new(Endpoint)
 | 
				
			||||||
		trimmedAddress string
 | 
							trimmedAddress string
 | 
				
			||||||
| 
						 | 
					@ -93,13 +97,16 @@ func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoin
 | 
				
			||||||
		address = "https://" + address
 | 
							address = "https://" + address
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						endpoint.IsSecure = (tlsConfig == nil || !tlsConfig.InsecureSkipVerify)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	trimmedAddress, endpoint.Version = scanForAPIVersion(address)
 | 
						trimmedAddress, endpoint.Version = scanForAPIVersion(address)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if endpoint.URL, err = url.Parse(trimmedAddress); err != nil {
 | 
						if endpoint.URL, err = url.Parse(trimmedAddress); err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	endpoint.IsSecure = secure
 | 
					
 | 
				
			||||||
	tr := NewTransport(ConnectTimeout, endpoint.IsSecure)
 | 
						// TODO(tiborvass): make sure a ConnectTimeout transport is used
 | 
				
			||||||
 | 
						tr := NewTransport(tlsConfig)
 | 
				
			||||||
	endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
 | 
						endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
 | 
				
			||||||
	return endpoint, nil
 | 
						return endpoint, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -166,7 +173,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) {
 | 
				
			||||||
func (e *Endpoint) pingV1() (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() == INDEXSERVER {
 | 
				
			||||||
		// Skip the check, we know this one is valid
 | 
							// Skip the check, we know this one is valid
 | 
				
			||||||
		// (and we never want to fallback to http in case of error)
 | 
							// (and we never want to fallback to http in case of error)
 | 
				
			||||||
		return RegistryInfo{Standalone: false}, nil
 | 
							return RegistryInfo{Standalone: false}, nil
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,14 +12,14 @@ func TestEndpointParse(t *testing.T) {
 | 
				
			||||||
		str      string
 | 
							str      string
 | 
				
			||||||
		expected string
 | 
							expected string
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{IndexServerAddress(), IndexServerAddress()},
 | 
							{INDEXSERVER, INDEXSERVER},
 | 
				
			||||||
		{"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"},
 | 
							{"http://0.0.0.0:5000/v1/", "http://0.0.0.0:5000/v1/"},
 | 
				
			||||||
		{"http://0.0.0.0:5000/v2/", "http://0.0.0.0:5000/v2/"},
 | 
							{"http://0.0.0.0:5000/v2/", "http://0.0.0.0:5000/v2/"},
 | 
				
			||||||
		{"http://0.0.0.0:5000", "http://0.0.0.0:5000/v0/"},
 | 
							{"http://0.0.0.0:5000", "http://0.0.0.0:5000/v0/"},
 | 
				
			||||||
		{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
 | 
							{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, td := range testData {
 | 
						for _, td := range testData {
 | 
				
			||||||
		e, err := newEndpoint(td.str, false, nil)
 | 
							e, err := newEndpoint(td.str, nil, nil)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			t.Errorf("%q: %s", td.str, err)
 | 
								t.Errorf("%q: %s", td.str, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ func TestValidateEndpointAmbiguousAPIVersion(t *testing.T) {
 | 
				
			||||||
	testEndpoint := Endpoint{
 | 
						testEndpoint := Endpoint{
 | 
				
			||||||
		URL:     testServerURL,
 | 
							URL:     testServerURL,
 | 
				
			||||||
		Version: APIVersionUnknown,
 | 
							Version: APIVersionUnknown,
 | 
				
			||||||
		client:  HTTPClient(NewTransport(ConnectTimeout, false)),
 | 
							client:  HTTPClient(NewTransport(nil)),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = validateEndpoint(&testEndpoint); err != nil {
 | 
						if err = validateEndpoint(&testEndpoint); err != nil {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										194
									
								
								docs/registry.go
								
								
								
								
							
							
						
						
									
										194
									
								
								docs/registry.go
								
								
								
								
							| 
						 | 
					@ -2,25 +2,20 @@ package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"crypto/x509"
 | 
					 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
					 | 
				
			||||||
	"path/filepath"
 | 
					 | 
				
			||||||
	"runtime"
 | 
						"runtime"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/Sirupsen/logrus"
 | 
						"github.com/Sirupsen/logrus"
 | 
				
			||||||
 | 
						"github.com/docker/distribution/registry/api/errcode"
 | 
				
			||||||
 | 
						"github.com/docker/distribution/registry/api/v2"
 | 
				
			||||||
	"github.com/docker/distribution/registry/client/transport"
 | 
						"github.com/docker/distribution/registry/client/transport"
 | 
				
			||||||
	"github.com/docker/docker/autogen/dockerversion"
 | 
						"github.com/docker/docker/autogen/dockerversion"
 | 
				
			||||||
	"github.com/docker/docker/pkg/parsers/kernel"
 | 
						"github.com/docker/docker/pkg/parsers/kernel"
 | 
				
			||||||
	"github.com/docker/docker/pkg/timeoutconn"
 | 
					 | 
				
			||||||
	"github.com/docker/docker/pkg/tlsconfig"
 | 
						"github.com/docker/docker/pkg/tlsconfig"
 | 
				
			||||||
	"github.com/docker/docker/pkg/useragent"
 | 
						"github.com/docker/docker/pkg/useragent"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -57,135 +52,13 @@ func init() {
 | 
				
			||||||
	dockerUserAgent = useragent.AppendVersions("", httpVersion...)
 | 
						dockerUserAgent = useragent.AppendVersions("", httpVersion...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type httpsRequestModifier struct {
 | 
					func hasFile(files []os.FileInfo, name string) bool {
 | 
				
			||||||
	mu        sync.Mutex
 | 
						for _, f := range files {
 | 
				
			||||||
	tlsConfig *tls.Config
 | 
							if f.Name() == name {
 | 
				
			||||||
}
 | 
								return true
 | 
				
			||||||
 | 
					 | 
				
			||||||
// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
 | 
					 | 
				
			||||||
// it's because it's so as to match the current behavior in master: we generate the
 | 
					 | 
				
			||||||
// certpool on every-goddam-request. It's not great, but it allows people to just put
 | 
					 | 
				
			||||||
// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
 | 
					 | 
				
			||||||
// prefer an fsnotify implementation, but that was out of scope of my refactoring.
 | 
					 | 
				
			||||||
func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
 | 
					 | 
				
			||||||
	var (
 | 
					 | 
				
			||||||
		roots   *x509.CertPool
 | 
					 | 
				
			||||||
		certs   []tls.Certificate
 | 
					 | 
				
			||||||
		hostDir string
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if req.URL.Scheme == "https" {
 | 
					 | 
				
			||||||
		hasFile := func(files []os.FileInfo, name string) bool {
 | 
					 | 
				
			||||||
			for _, f := range files {
 | 
					 | 
				
			||||||
				if f.Name() == name {
 | 
					 | 
				
			||||||
					return true
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			return false
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if runtime.GOOS == "windows" {
 | 
					 | 
				
			||||||
			hostDir = path.Join(os.TempDir(), "/docker/certs.d", req.URL.Host)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			hostDir = path.Join("/etc/docker/certs.d", req.URL.Host)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		logrus.Debugf("hostDir: %s", hostDir)
 | 
					 | 
				
			||||||
		fs, err := ioutil.ReadDir(hostDir)
 | 
					 | 
				
			||||||
		if err != nil && !os.IsNotExist(err) {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for _, f := range fs {
 | 
					 | 
				
			||||||
			if strings.HasSuffix(f.Name(), ".crt") {
 | 
					 | 
				
			||||||
				if roots == nil {
 | 
					 | 
				
			||||||
					roots = x509.NewCertPool()
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
 | 
					 | 
				
			||||||
				data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				roots.AppendCertsFromPEM(data)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if strings.HasSuffix(f.Name(), ".cert") {
 | 
					 | 
				
			||||||
				certName := f.Name()
 | 
					 | 
				
			||||||
				keyName := certName[:len(certName)-5] + ".key"
 | 
					 | 
				
			||||||
				logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
 | 
					 | 
				
			||||||
				if !hasFile(fs, keyName) {
 | 
					 | 
				
			||||||
					return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), path.Join(hostDir, keyName))
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return err
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				certs = append(certs, cert)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if strings.HasSuffix(f.Name(), ".key") {
 | 
					 | 
				
			||||||
				keyName := f.Name()
 | 
					 | 
				
			||||||
				certName := keyName[:len(keyName)-4] + ".cert"
 | 
					 | 
				
			||||||
				logrus.Debugf("key: %s", hostDir+"/"+f.Name())
 | 
					 | 
				
			||||||
				if !hasFile(fs, certName) {
 | 
					 | 
				
			||||||
					return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		m.mu.Lock()
 | 
					 | 
				
			||||||
		m.tlsConfig.RootCAs = roots
 | 
					 | 
				
			||||||
		m.tlsConfig.Certificates = certs
 | 
					 | 
				
			||||||
		m.mu.Unlock()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
 | 
					 | 
				
			||||||
	tlsConfig := &tls.Config{
 | 
					 | 
				
			||||||
		// Avoid fallback to SSL protocols < TLS1.0
 | 
					 | 
				
			||||||
		MinVersion:         tls.VersionTLS10,
 | 
					 | 
				
			||||||
		InsecureSkipVerify: !secure,
 | 
					 | 
				
			||||||
		CipherSuites:       tlsconfig.DefaultServerAcceptedCiphers,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	tr := &http.Transport{
 | 
					 | 
				
			||||||
		DisableKeepAlives: true,
 | 
					 | 
				
			||||||
		Proxy:             http.ProxyFromEnvironment,
 | 
					 | 
				
			||||||
		TLSClientConfig:   tlsConfig,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	switch timeout {
 | 
					 | 
				
			||||||
	case ConnectTimeout:
 | 
					 | 
				
			||||||
		tr.Dial = func(proto string, addr string) (net.Conn, error) {
 | 
					 | 
				
			||||||
			// Set the connect timeout to 30 seconds to allow for slower connection
 | 
					 | 
				
			||||||
			// times...
 | 
					 | 
				
			||||||
			d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			conn, err := d.Dial(proto, addr)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			// Set the recv timeout to 10 seconds
 | 
					 | 
				
			||||||
			conn.SetDeadline(time.Now().Add(10 * time.Second))
 | 
					 | 
				
			||||||
			return conn, nil
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case ReceiveTimeout:
 | 
					 | 
				
			||||||
		tr.Dial = func(proto string, addr string) (net.Conn, error) {
 | 
					 | 
				
			||||||
			d := net.Dialer{DualStack: true}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			conn, err := d.Dial(proto, addr)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, err
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			conn = timeoutconn.New(conn, 1*time.Minute)
 | 
					 | 
				
			||||||
			return conn, nil
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
	if secure {
 | 
					 | 
				
			||||||
		// note: httpsTransport also handles http transport
 | 
					 | 
				
			||||||
		// but for HTTPS, it sets up the certs
 | 
					 | 
				
			||||||
		return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig: tlsConfig})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return tr
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DockerHeaders returns request modifiers that ensure requests have
 | 
					// DockerHeaders returns request modifiers that ensure requests have
 | 
				
			||||||
| 
						 | 
					@ -202,10 +75,6 @@ func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func HTTPClient(transport http.RoundTripper) *http.Client {
 | 
					func HTTPClient(transport http.RoundTripper) *http.Client {
 | 
				
			||||||
	if transport == nil {
 | 
					 | 
				
			||||||
		transport = NewTransport(ConnectTimeout, true)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &http.Client{
 | 
						return &http.Client{
 | 
				
			||||||
		Transport:     transport,
 | 
							Transport:     transport,
 | 
				
			||||||
		CheckRedirect: AddRequiredHeadersToRedirectedRequests,
 | 
							CheckRedirect: AddRequiredHeadersToRedirectedRequests,
 | 
				
			||||||
| 
						 | 
					@ -245,3 +114,52 @@ func AddRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Reque
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func shouldV2Fallback(err errcode.Error) bool {
 | 
				
			||||||
 | 
						logrus.Debugf("v2 error: %T %v", err, err)
 | 
				
			||||||
 | 
						switch err.Code {
 | 
				
			||||||
 | 
						case v2.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown:
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ErrNoSupport struct{ Err error }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e ErrNoSupport) Error() string {
 | 
				
			||||||
 | 
						if e.Err == nil {
 | 
				
			||||||
 | 
							return "not supported"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return e.Err.Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ContinueOnError(err error) bool {
 | 
				
			||||||
 | 
						switch v := err.(type) {
 | 
				
			||||||
 | 
						case errcode.Errors:
 | 
				
			||||||
 | 
							return ContinueOnError(v[0])
 | 
				
			||||||
 | 
						case ErrNoSupport:
 | 
				
			||||||
 | 
							return ContinueOnError(v.Err)
 | 
				
			||||||
 | 
						case errcode.Error:
 | 
				
			||||||
 | 
							return shouldV2Fallback(v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewTransport(tlsConfig *tls.Config) *http.Transport {
 | 
				
			||||||
 | 
						if tlsConfig == nil {
 | 
				
			||||||
 | 
							var cfg = tlsconfig.ServerDefault
 | 
				
			||||||
 | 
							tlsConfig = &cfg
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &http.Transport{
 | 
				
			||||||
 | 
							Proxy: http.ProxyFromEnvironment,
 | 
				
			||||||
 | 
							Dial: (&net.Dialer{
 | 
				
			||||||
 | 
								Timeout:   30 * time.Second,
 | 
				
			||||||
 | 
								KeepAlive: 30 * time.Second,
 | 
				
			||||||
 | 
								DualStack: true,
 | 
				
			||||||
 | 
							}).Dial,
 | 
				
			||||||
 | 
							TLSHandshakeTimeout: 10 * time.Second,
 | 
				
			||||||
 | 
							TLSClientConfig:     tlsConfig,
 | 
				
			||||||
 | 
							// TODO(dmcgowan): Call close idle connections when complete and use keep alive
 | 
				
			||||||
 | 
							DisableKeepAlives: true,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -165,7 +165,7 @@ func makeHttpsIndex(req string) *IndexInfo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func makePublicIndex() *IndexInfo {
 | 
					func makePublicIndex() *IndexInfo {
 | 
				
			||||||
	index := &IndexInfo{
 | 
						index := &IndexInfo{
 | 
				
			||||||
		Name:     IndexServerAddress(),
 | 
							Name:     INDEXSERVER,
 | 
				
			||||||
		Secure:   true,
 | 
							Secure:   true,
 | 
				
			||||||
		Official: true,
 | 
							Official: true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatal(err)
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var tr http.RoundTripper = debugTransport{NewTransport(ReceiveTimeout, endpoint.IsSecure), t.Log}
 | 
						var tr http.RoundTripper = debugTransport{NewTransport(nil), t.Log}
 | 
				
			||||||
	tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
 | 
						tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
 | 
				
			||||||
	client := HTTPClient(tr)
 | 
						client := HTTPClient(tr)
 | 
				
			||||||
	r, err := NewSession(client, authConfig, endpoint)
 | 
						r, err := NewSession(client, authConfig, endpoint)
 | 
				
			||||||
| 
						 | 
					@ -332,7 +332,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
	expectedRepoInfos := map[string]RepositoryInfo{
 | 
						expectedRepoInfos := map[string]RepositoryInfo{
 | 
				
			||||||
		"fooo/bar": {
 | 
							"fooo/bar": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "fooo/bar",
 | 
								RemoteName:    "fooo/bar",
 | 
				
			||||||
| 
						 | 
					@ -342,7 +342,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"library/ubuntu": {
 | 
							"library/ubuntu": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "library/ubuntu",
 | 
								RemoteName:    "library/ubuntu",
 | 
				
			||||||
| 
						 | 
					@ -352,7 +352,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"nonlibrary/ubuntu": {
 | 
							"nonlibrary/ubuntu": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "nonlibrary/ubuntu",
 | 
								RemoteName:    "nonlibrary/ubuntu",
 | 
				
			||||||
| 
						 | 
					@ -362,7 +362,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"ubuntu": {
 | 
							"ubuntu": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "library/ubuntu",
 | 
								RemoteName:    "library/ubuntu",
 | 
				
			||||||
| 
						 | 
					@ -372,7 +372,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"other/library": {
 | 
							"other/library": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "other/library",
 | 
								RemoteName:    "other/library",
 | 
				
			||||||
| 
						 | 
					@ -480,9 +480,9 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
			CanonicalName: "localhost/privatebase",
 | 
								CanonicalName: "localhost/privatebase",
 | 
				
			||||||
			Official:      false,
 | 
								Official:      false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		IndexServerName() + "/public/moonbase": {
 | 
							INDEXNAME + "/public/moonbase": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "public/moonbase",
 | 
								RemoteName:    "public/moonbase",
 | 
				
			||||||
| 
						 | 
					@ -490,19 +490,9 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
			CanonicalName: "docker.io/public/moonbase",
 | 
								CanonicalName: "docker.io/public/moonbase",
 | 
				
			||||||
			Official:      false,
 | 
								Official:      false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"index." + IndexServerName() + "/public/moonbase": {
 | 
							"index." + INDEXNAME + "/public/moonbase": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			RemoteName:    "public/moonbase",
 | 
					 | 
				
			||||||
			LocalName:     "public/moonbase",
 | 
					 | 
				
			||||||
			CanonicalName: "docker.io/public/moonbase",
 | 
					 | 
				
			||||||
			Official:      false,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		IndexServerName() + "/public/moonbase": {
 | 
					 | 
				
			||||||
			Index: &IndexInfo{
 | 
					 | 
				
			||||||
				Name:     IndexServerName(),
 | 
					 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "public/moonbase",
 | 
								RemoteName:    "public/moonbase",
 | 
				
			||||||
| 
						 | 
					@ -512,7 +502,7 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"ubuntu-12.04-base": {
 | 
							"ubuntu-12.04-base": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "library/ubuntu-12.04-base",
 | 
								RemoteName:    "library/ubuntu-12.04-base",
 | 
				
			||||||
| 
						 | 
					@ -520,9 +510,9 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
			CanonicalName: "docker.io/library/ubuntu-12.04-base",
 | 
								CanonicalName: "docker.io/library/ubuntu-12.04-base",
 | 
				
			||||||
			Official:      true,
 | 
								Official:      true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		IndexServerName() + "/ubuntu-12.04-base": {
 | 
							INDEXNAME + "/ubuntu-12.04-base": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "library/ubuntu-12.04-base",
 | 
								RemoteName:    "library/ubuntu-12.04-base",
 | 
				
			||||||
| 
						 | 
					@ -530,19 +520,9 @@ func TestParseRepositoryInfo(t *testing.T) {
 | 
				
			||||||
			CanonicalName: "docker.io/library/ubuntu-12.04-base",
 | 
								CanonicalName: "docker.io/library/ubuntu-12.04-base",
 | 
				
			||||||
			Official:      true,
 | 
								Official:      true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		IndexServerName() + "/ubuntu-12.04-base": {
 | 
							"index." + INDEXNAME + "/ubuntu-12.04-base": {
 | 
				
			||||||
			Index: &IndexInfo{
 | 
								Index: &IndexInfo{
 | 
				
			||||||
				Name:     IndexServerName(),
 | 
									Name:     INDEXNAME,
 | 
				
			||||||
				Official: true,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			RemoteName:    "library/ubuntu-12.04-base",
 | 
					 | 
				
			||||||
			LocalName:     "ubuntu-12.04-base",
 | 
					 | 
				
			||||||
			CanonicalName: "docker.io/library/ubuntu-12.04-base",
 | 
					 | 
				
			||||||
			Official:      true,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		"index." + IndexServerName() + "/ubuntu-12.04-base": {
 | 
					 | 
				
			||||||
			Index: &IndexInfo{
 | 
					 | 
				
			||||||
				Name:     IndexServerName(),
 | 
					 | 
				
			||||||
				Official: true,
 | 
									Official: true,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			RemoteName:    "library/ubuntu-12.04-base",
 | 
								RemoteName:    "library/ubuntu-12.04-base",
 | 
				
			||||||
| 
						 | 
					@ -585,14 +565,14 @@ func TestNewIndexInfo(t *testing.T) {
 | 
				
			||||||
	config := NewServiceConfig(nil)
 | 
						config := NewServiceConfig(nil)
 | 
				
			||||||
	noMirrors := make([]string, 0)
 | 
						noMirrors := make([]string, 0)
 | 
				
			||||||
	expectedIndexInfos := map[string]*IndexInfo{
 | 
						expectedIndexInfos := map[string]*IndexInfo{
 | 
				
			||||||
		IndexServerName(): {
 | 
							INDEXNAME: {
 | 
				
			||||||
			Name:     IndexServerName(),
 | 
								Name:     INDEXNAME,
 | 
				
			||||||
			Official: true,
 | 
								Official: true,
 | 
				
			||||||
			Secure:   true,
 | 
								Secure:   true,
 | 
				
			||||||
			Mirrors:  noMirrors,
 | 
								Mirrors:  noMirrors,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"index." + IndexServerName(): {
 | 
							"index." + INDEXNAME: {
 | 
				
			||||||
			Name:     IndexServerName(),
 | 
								Name:     INDEXNAME,
 | 
				
			||||||
			Official: true,
 | 
								Official: true,
 | 
				
			||||||
			Secure:   true,
 | 
								Secure:   true,
 | 
				
			||||||
			Mirrors:  noMirrors,
 | 
								Mirrors:  noMirrors,
 | 
				
			||||||
| 
						 | 
					@ -616,14 +596,14 @@ func TestNewIndexInfo(t *testing.T) {
 | 
				
			||||||
	config = makeServiceConfig(publicMirrors, []string{"example.com"})
 | 
						config = makeServiceConfig(publicMirrors, []string{"example.com"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expectedIndexInfos = map[string]*IndexInfo{
 | 
						expectedIndexInfos = map[string]*IndexInfo{
 | 
				
			||||||
		IndexServerName(): {
 | 
							INDEXNAME: {
 | 
				
			||||||
			Name:     IndexServerName(),
 | 
								Name:     INDEXNAME,
 | 
				
			||||||
			Official: true,
 | 
								Official: true,
 | 
				
			||||||
			Secure:   true,
 | 
								Secure:   true,
 | 
				
			||||||
			Mirrors:  publicMirrors,
 | 
								Mirrors:  publicMirrors,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		"index." + IndexServerName(): {
 | 
							"index." + INDEXNAME: {
 | 
				
			||||||
			Name:     IndexServerName(),
 | 
								Name:     INDEXNAME,
 | 
				
			||||||
			Official: true,
 | 
								Official: true,
 | 
				
			||||||
			Secure:   true,
 | 
								Secure:   true,
 | 
				
			||||||
			Mirrors:  publicMirrors,
 | 
								Mirrors:  publicMirrors,
 | 
				
			||||||
| 
						 | 
					@ -880,7 +860,7 @@ func TestIsSecureIndex(t *testing.T) {
 | 
				
			||||||
		insecureRegistries []string
 | 
							insecureRegistries []string
 | 
				
			||||||
		expected           bool
 | 
							expected           bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{IndexServerName(), nil, true},
 | 
							{INDEXNAME, nil, true},
 | 
				
			||||||
		{"example.com", []string{}, true},
 | 
							{"example.com", []string{}, true},
 | 
				
			||||||
		{"example.com", []string{"example.com"}, false},
 | 
							{"example.com", []string{"example.com"}, false},
 | 
				
			||||||
		{"localhost", []string{"localhost:5000"}, false},
 | 
							{"localhost", []string{"localhost:5000"}, false},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										195
									
								
								docs/service.go
								
								
								
								
							
							
						
						
									
										195
									
								
								docs/service.go
								
								
								
								
							| 
						 | 
					@ -1,9 +1,19 @@
 | 
				
			||||||
package registry
 | 
					package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Sirupsen/logrus"
 | 
				
			||||||
 | 
						"github.com/docker/distribution/registry/client/auth"
 | 
				
			||||||
	"github.com/docker/docker/cliconfig"
 | 
						"github.com/docker/docker/cliconfig"
 | 
				
			||||||
 | 
						"github.com/docker/docker/pkg/tlsconfig"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Service struct {
 | 
					type Service struct {
 | 
				
			||||||
| 
						 | 
					@ -25,7 +35,7 @@ func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) {
 | 
				
			||||||
	addr := authConfig.ServerAddress
 | 
						addr := authConfig.ServerAddress
 | 
				
			||||||
	if addr == "" {
 | 
						if addr == "" {
 | 
				
			||||||
		// Use the official registry address if not specified.
 | 
							// Use the official registry address if not specified.
 | 
				
			||||||
		addr = IndexServerAddress()
 | 
							addr = INDEXSERVER
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	index, err := s.ResolveIndex(addr)
 | 
						index, err := s.ResolveIndex(addr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
| 
						 | 
					@ -69,3 +79,186 @@ func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) {
 | 
				
			||||||
func (s *Service) ResolveIndex(name string) (*IndexInfo, error) {
 | 
					func (s *Service) ResolveIndex(name string) (*IndexInfo, error) {
 | 
				
			||||||
	return s.Config.NewIndexInfo(name)
 | 
						return s.Config.NewIndexInfo(name)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type APIEndpoint struct {
 | 
				
			||||||
 | 
						Mirror        bool
 | 
				
			||||||
 | 
						URL           string
 | 
				
			||||||
 | 
						Version       APIVersion
 | 
				
			||||||
 | 
						Official      bool
 | 
				
			||||||
 | 
						TrimHostname  bool
 | 
				
			||||||
 | 
						TLSConfig     *tls.Config
 | 
				
			||||||
 | 
						VersionHeader string
 | 
				
			||||||
 | 
						Versions      []auth.APIVersion
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e APIEndpoint) ToV1Endpoint(metaHeaders http.Header) (*Endpoint, error) {
 | 
				
			||||||
 | 
						return newEndpoint(e.URL, e.TLSConfig, metaHeaders)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Service) TlsConfig(hostname string) (*tls.Config, error) {
 | 
				
			||||||
 | 
						// we construct a client tls config from server defaults
 | 
				
			||||||
 | 
						// PreferredServerCipherSuites should have no effect
 | 
				
			||||||
 | 
						tlsConfig := tlsconfig.ServerDefault
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isSecure := s.Config.isSecureIndex(hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tlsConfig.InsecureSkipVerify = !isSecure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isSecure {
 | 
				
			||||||
 | 
							hasFile := func(files []os.FileInfo, name string) bool {
 | 
				
			||||||
 | 
								for _, f := range files {
 | 
				
			||||||
 | 
									if f.Name() == name {
 | 
				
			||||||
 | 
										return true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hostDir := filepath.Join(CERTS_DIR, hostname)
 | 
				
			||||||
 | 
							logrus.Debugf("hostDir: %s", hostDir)
 | 
				
			||||||
 | 
							fs, err := ioutil.ReadDir(hostDir)
 | 
				
			||||||
 | 
							if err != nil && !os.IsNotExist(err) {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, f := range fs {
 | 
				
			||||||
 | 
								if strings.HasSuffix(f.Name(), ".crt") {
 | 
				
			||||||
 | 
									if tlsConfig.RootCAs == nil {
 | 
				
			||||||
 | 
										// TODO(dmcgowan): Copy system pool
 | 
				
			||||||
 | 
										tlsConfig.RootCAs = x509.NewCertPool()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									logrus.Debugf("crt: %s", filepath.Join(hostDir, f.Name()))
 | 
				
			||||||
 | 
									data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tlsConfig.RootCAs.AppendCertsFromPEM(data)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if strings.HasSuffix(f.Name(), ".cert") {
 | 
				
			||||||
 | 
									certName := f.Name()
 | 
				
			||||||
 | 
									keyName := certName[:len(certName)-5] + ".key"
 | 
				
			||||||
 | 
									logrus.Debugf("cert: %s", filepath.Join(hostDir, f.Name()))
 | 
				
			||||||
 | 
									if !hasFile(fs, keyName) {
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), filepath.Join(hostDir, keyName))
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return nil, err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if strings.HasSuffix(f.Name(), ".key") {
 | 
				
			||||||
 | 
									keyName := f.Name()
 | 
				
			||||||
 | 
									certName := keyName[:len(keyName)-4] + ".cert"
 | 
				
			||||||
 | 
									logrus.Debugf("key: %s", filepath.Join(hostDir, f.Name()))
 | 
				
			||||||
 | 
									if !hasFile(fs, certName) {
 | 
				
			||||||
 | 
										return nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &tlsConfig, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Service) LookupEndpoints(repoName string) (endpoints []APIEndpoint, err error) {
 | 
				
			||||||
 | 
						var cfg = tlsconfig.ServerDefault
 | 
				
			||||||
 | 
						tlsConfig := &cfg
 | 
				
			||||||
 | 
						if strings.HasPrefix(repoName, DEFAULT_NAMESPACE+"/") {
 | 
				
			||||||
 | 
							// v2 mirrors
 | 
				
			||||||
 | 
							for _, mirror := range s.Config.Mirrors {
 | 
				
			||||||
 | 
								endpoints = append(endpoints, APIEndpoint{
 | 
				
			||||||
 | 
									URL: mirror,
 | 
				
			||||||
 | 
									// guess mirrors are v2
 | 
				
			||||||
 | 
									Version:      APIVersion2,
 | 
				
			||||||
 | 
									Mirror:       true,
 | 
				
			||||||
 | 
									TrimHostname: true,
 | 
				
			||||||
 | 
									TLSConfig:    tlsConfig,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// v2 registry
 | 
				
			||||||
 | 
							endpoints = append(endpoints, APIEndpoint{
 | 
				
			||||||
 | 
								URL:          DEFAULT_V2_REGISTRY,
 | 
				
			||||||
 | 
								Version:      APIVersion2,
 | 
				
			||||||
 | 
								Official:     true,
 | 
				
			||||||
 | 
								TrimHostname: true,
 | 
				
			||||||
 | 
								TLSConfig:    tlsConfig,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							// v1 mirrors
 | 
				
			||||||
 | 
							// TODO(tiborvass): shouldn't we remove v1 mirrors from here, since v1 mirrors are kinda special?
 | 
				
			||||||
 | 
							for _, mirror := range s.Config.Mirrors {
 | 
				
			||||||
 | 
								endpoints = append(endpoints, APIEndpoint{
 | 
				
			||||||
 | 
									URL: mirror,
 | 
				
			||||||
 | 
									// guess mirrors are v1
 | 
				
			||||||
 | 
									Version:      APIVersion1,
 | 
				
			||||||
 | 
									Mirror:       true,
 | 
				
			||||||
 | 
									TrimHostname: true,
 | 
				
			||||||
 | 
									TLSConfig:    tlsConfig,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// v1 registry
 | 
				
			||||||
 | 
							endpoints = append(endpoints, APIEndpoint{
 | 
				
			||||||
 | 
								URL:          DEFAULT_V1_REGISTRY,
 | 
				
			||||||
 | 
								Version:      APIVersion1,
 | 
				
			||||||
 | 
								Official:     true,
 | 
				
			||||||
 | 
								TrimHostname: true,
 | 
				
			||||||
 | 
								TLSConfig:    tlsConfig,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							return endpoints, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						slashIndex := strings.IndexRune(repoName, '/')
 | 
				
			||||||
 | 
						if slashIndex <= 0 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("invalid repo name: missing '/':  %s", repoName)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						hostname := repoName[:slashIndex]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tlsConfig, err = s.TlsConfig(hostname)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						isSecure := !tlsConfig.InsecureSkipVerify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						v2Versions := []auth.APIVersion{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Type:    "registry",
 | 
				
			||||||
 | 
								Version: "2.0",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						endpoints = []APIEndpoint{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								URL:           "https://" + hostname,
 | 
				
			||||||
 | 
								Version:       APIVersion2,
 | 
				
			||||||
 | 
								TrimHostname:  true,
 | 
				
			||||||
 | 
								TLSConfig:     tlsConfig,
 | 
				
			||||||
 | 
								VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER,
 | 
				
			||||||
 | 
								Versions:      v2Versions,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								URL:          "https://" + hostname,
 | 
				
			||||||
 | 
								Version:      APIVersion1,
 | 
				
			||||||
 | 
								TrimHostname: true,
 | 
				
			||||||
 | 
								TLSConfig:    tlsConfig,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !isSecure {
 | 
				
			||||||
 | 
							endpoints = append(endpoints, APIEndpoint{
 | 
				
			||||||
 | 
								URL:          "http://" + hostname,
 | 
				
			||||||
 | 
								Version:      APIVersion2,
 | 
				
			||||||
 | 
								TrimHostname: true,
 | 
				
			||||||
 | 
								// used to check if supposed to be secure via InsecureSkipVerify
 | 
				
			||||||
 | 
								TLSConfig:     tlsConfig,
 | 
				
			||||||
 | 
								VersionHeader: DEFAULT_REGISTRY_VERSION_HEADER,
 | 
				
			||||||
 | 
								Versions:      v2Versions,
 | 
				
			||||||
 | 
							}, APIEndpoint{
 | 
				
			||||||
 | 
								URL:          "http://" + hostname,
 | 
				
			||||||
 | 
								Version:      APIVersion1,
 | 
				
			||||||
 | 
								TrimHostname: true,
 | 
				
			||||||
 | 
								// used to check if supposed to be secure via InsecureSkipVerify
 | 
				
			||||||
 | 
								TLSConfig: tlsConfig,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return endpoints, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,7 +98,7 @@ func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
 | 
				
			||||||
		return tr.RoundTripper.RoundTrip(orig)
 | 
							return tr.RoundTripper.RoundTrip(orig)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	req := transport.CloneRequest(orig)
 | 
						req := cloneRequest(orig)
 | 
				
			||||||
	tr.mu.Lock()
 | 
						tr.mu.Lock()
 | 
				
			||||||
	tr.modReq[orig] = req
 | 
						tr.modReq[orig] = req
 | 
				
			||||||
	tr.mu.Unlock()
 | 
						tr.mu.Unlock()
 | 
				
			||||||
| 
						 | 
					@ -164,12 +164,11 @@ func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 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 all our requests.
 | 
						// alongside all our requests.
 | 
				
			||||||
	if endpoint.VersionString(1) != IndexServerAddress() && endpoint.URL.Scheme == "https" {
 | 
						if endpoint.VersionString(1) != INDEXSERVER && endpoint.URL.Scheme == "https" {
 | 
				
			||||||
		info, err := endpoint.Ping()
 | 
							info, err := endpoint.Ping()
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, err
 | 
								return nil, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if info.Standalone && authConfig != nil {
 | 
							if info.Standalone && authConfig != nil {
 | 
				
			||||||
			logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
 | 
								logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
 | 
				
			||||||
			alwaysSetBasicAuth = true
 | 
								alwaysSetBasicAuth = true
 | 
				
			||||||
| 
						 | 
					@ -265,7 +264,7 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("Error while getting from the server: %v", err)
 | 
							return nil, fmt.Errorf("Error while getting from the server: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// TODO: why are we doing retries at this level?
 | 
						// TODO(tiborvass): why are we doing retries at this level?
 | 
				
			||||||
	// These retries should be generic to both v1 and v2
 | 
						// 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
 | 
				
			||||||
| 
						 | 
					@ -432,7 +431,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Forge a better object from the retrieved data
 | 
						// Forge a better object from the retrieved data
 | 
				
			||||||
	imgsData := make(map[string]*ImgData)
 | 
						imgsData := make(map[string]*ImgData, len(remoteChecksums))
 | 
				
			||||||
	for _, elem := range remoteChecksums {
 | 
						for _, elem := range remoteChecksums {
 | 
				
			||||||
		imgsData[elem.ID] = elem
 | 
							imgsData[elem.ID] = elem
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,414 +0,0 @@
 | 
				
			||||||
package registry
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"io"
 | 
					 | 
				
			||||||
	"io/ioutil"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	"github.com/Sirupsen/logrus"
 | 
					 | 
				
			||||||
	"github.com/docker/distribution/digest"
 | 
					 | 
				
			||||||
	"github.com/docker/distribution/registry/api/v2"
 | 
					 | 
				
			||||||
	"github.com/docker/docker/pkg/httputils"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const DockerDigestHeader = "Docker-Content-Digest"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getV2Builder(e *Endpoint) *v2.URLBuilder {
 | 
					 | 
				
			||||||
	if e.URLBuilder == nil {
 | 
					 | 
				
			||||||
		e.URLBuilder = v2.NewURLBuilder(e.URL)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return e.URLBuilder
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
 | 
					 | 
				
			||||||
	// TODO check if should use Mirror
 | 
					 | 
				
			||||||
	if index.Official {
 | 
					 | 
				
			||||||
		ep, err = newEndpoint(REGISTRYSERVER, true, nil)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		err = validateEndpoint(ep)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
 | 
					 | 
				
			||||||
		ep = r.indexEndpoint
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		ep, err = NewEndpoint(index, nil)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ep.URLBuilder = v2.NewURLBuilder(ep.URL)
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetV2Authorization gets the authorization needed to the given image
 | 
					 | 
				
			||||||
// If readonly access is requested, then the authorization may
 | 
					 | 
				
			||||||
// only be used for Get operations.
 | 
					 | 
				
			||||||
func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bool) (auth *RequestAuthorization, err error) {
 | 
					 | 
				
			||||||
	scopes := []string{"pull"}
 | 
					 | 
				
			||||||
	if !readOnly {
 | 
					 | 
				
			||||||
		scopes = append(scopes, "push")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logrus.Debugf("Getting authorization for %s %s", imageName, scopes)
 | 
					 | 
				
			||||||
	return NewRequestAuthorization(r.GetAuthConfig(true), ep, "repository", imageName, scopes), nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// 1) Check if TarSum of each layer exists /v2/
 | 
					 | 
				
			||||||
//  1.a) if 200, continue
 | 
					 | 
				
			||||||
//  1.b) if 300, then push the
 | 
					 | 
				
			||||||
//  1.c) if anything else, err
 | 
					 | 
				
			||||||
// 2) PUT the created/signed manifest
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetV2ImageManifest simply fetches the bytes of a manifest and the remote
 | 
					 | 
				
			||||||
// digest, if available in the request. Note that the application shouldn't
 | 
					 | 
				
			||||||
// rely on the untrusted remoteDigest, and should also verify against a
 | 
					 | 
				
			||||||
// locally provided digest, if applicable.
 | 
					 | 
				
			||||||
func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) (remoteDigest digest.Digest, p []byte, err error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "GET"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return "", nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if res.StatusCode != 200 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return "", nil, errLoginRequired
 | 
					 | 
				
			||||||
		} else if res.StatusCode == 404 {
 | 
					 | 
				
			||||||
			return "", nil, ErrDoesNotExist
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return "", nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	p, err = ioutil.ReadAll(res.Body)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", nil, fmt.Errorf("Error while reading the http response: %s", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	dgstHdr := res.Header.Get(DockerDigestHeader)
 | 
					 | 
				
			||||||
	if dgstHdr != "" {
 | 
					 | 
				
			||||||
		remoteDigest, err = digest.ParseDigest(dgstHdr)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			// NOTE(stevvooe): Including the remote digest is optional. We
 | 
					 | 
				
			||||||
			// don't need to verify against it, but it is good practice.
 | 
					 | 
				
			||||||
			remoteDigest = ""
 | 
					 | 
				
			||||||
			logrus.Debugf("error parsing remote digest when fetching %v: %v", routeURL, err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - Succeeded to head image blob (already exists)
 | 
					 | 
				
			||||||
// - Failed with no error (continue to Push the Blob)
 | 
					 | 
				
			||||||
// - Failed with error
 | 
					 | 
				
			||||||
func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "HEAD"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return false, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res.Body.Close() // close early, since we're not needing a body on this call .. yet?
 | 
					 | 
				
			||||||
	switch {
 | 
					 | 
				
			||||||
	case res.StatusCode >= 200 && res.StatusCode < 400:
 | 
					 | 
				
			||||||
		// return something indicating no push needed
 | 
					 | 
				
			||||||
		return true, nil
 | 
					 | 
				
			||||||
	case res.StatusCode == 401:
 | 
					 | 
				
			||||||
		return false, errLoginRequired
 | 
					 | 
				
			||||||
	case res.StatusCode == 404:
 | 
					 | 
				
			||||||
		// return something indicating blob push needed
 | 
					 | 
				
			||||||
		return false, nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "GET"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
	if res.StatusCode != 200 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return errLoginRequired
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	_, err = io.Copy(blobWrtr, res.Body)
 | 
					 | 
				
			||||||
	return err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, 0, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "GET"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, 0, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return nil, 0, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, 0, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if res.StatusCode != 200 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return nil, 0, errLoginRequired
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	lenStr := res.Header.Get("Content-Length")
 | 
					 | 
				
			||||||
	l, err := strconv.ParseInt(lenStr, 10, 64)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, 0, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return res.Body, l, err
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Push the image to the server for storage.
 | 
					 | 
				
			||||||
// 'layer' is an uncompressed reader of the blob to be pushed.
 | 
					 | 
				
			||||||
// The server will generate it's own checksum calculation.
 | 
					 | 
				
			||||||
func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error {
 | 
					 | 
				
			||||||
	location, err := r.initiateBlobUpload(ep, imageName, auth)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "PUT"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, location)
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	queryParams := req.URL.Query()
 | 
					 | 
				
			||||||
	queryParams.Add("digest", dgst.String())
 | 
					 | 
				
			||||||
	req.URL.RawQuery = queryParams.Encode()
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if res.StatusCode != 201 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return errLoginRequired
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		errBody, err := ioutil.ReadAll(res.Body)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
 | 
					 | 
				
			||||||
		return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// initiateBlobUpload gets the blob upload location for the given image name.
 | 
					 | 
				
			||||||
func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *RequestAuthorization) (location string, err error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildBlobUploadURL(imageName)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
 | 
					 | 
				
			||||||
	req, err := http.NewRequest("POST", routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if res.StatusCode != http.StatusAccepted {
 | 
					 | 
				
			||||||
		if res.StatusCode == http.StatusUnauthorized {
 | 
					 | 
				
			||||||
			return "", errLoginRequired
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if res.StatusCode == http.StatusNotFound {
 | 
					 | 
				
			||||||
			return "", ErrDoesNotExist
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		errBody, err := ioutil.ReadAll(res.Body)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return "", err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
 | 
					 | 
				
			||||||
		return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if location = res.Header.Get("Location"); location == "" {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("registry did not return a Location header for resumable blob upload for image %s", imageName)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Finally Push the (signed) manifest of the blobs we've just pushed
 | 
					 | 
				
			||||||
func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "PUT"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// All 2xx and 3xx responses can be accepted for a put.
 | 
					 | 
				
			||||||
	if res.StatusCode >= 400 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return "", errLoginRequired
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		errBody, err := ioutil.ReadAll(res.Body)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
					 | 
				
			||||||
			return "", err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
 | 
					 | 
				
			||||||
		return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	dgstVerifier, err := digest.NewDigestVerifier(hdrDigest)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	dgstVerifier.Write(rawManifest)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !dgstVerifier.Verified() {
 | 
					 | 
				
			||||||
		computedDigest, _ := digest.FromBytes(rawManifest)
 | 
					 | 
				
			||||||
		return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return hdrDigest, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type remoteTags struct {
 | 
					 | 
				
			||||||
	Name string   `json:"name"`
 | 
					 | 
				
			||||||
	Tags []string `json:"tags"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Given a repository name, returns a json array of string tags
 | 
					 | 
				
			||||||
func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestAuthorization) ([]string, error) {
 | 
					 | 
				
			||||||
	routeURL, err := getV2Builder(ep).BuildTagsURL(imageName)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	method := "GET"
 | 
					 | 
				
			||||||
	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	req, err := http.NewRequest(method, routeURL, nil)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := auth.Authorize(req); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	res, err := r.client.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
	if res.StatusCode != 200 {
 | 
					 | 
				
			||||||
		if res.StatusCode == 401 {
 | 
					 | 
				
			||||||
			return nil, errLoginRequired
 | 
					 | 
				
			||||||
		} else if res.StatusCode == 404 {
 | 
					 | 
				
			||||||
			return nil, ErrDoesNotExist
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var remote remoteTags
 | 
					 | 
				
			||||||
	if err := json.NewDecoder(res.Body).Decode(&remote); err != nil {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("Error while decoding the http response: %s", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return remote.Tags, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
		Reference in New Issue