registry/service.go
3d605683
 package registry
 
73823e5e
 import (
19515a7a
 	"crypto/tls"
e896d1d7
 	"fmt"
73823e5e
 	"net/http"
cb57b256
 	"net/url"
4352da78
 	"strings"
73823e5e
 
636c276f
 	"golang.org/x/net/context"
 
f2d481a2
 	"github.com/Sirupsen/logrus"
e678d9fb
 	"github.com/docker/distribution/registry/client/auth"
2655954c
 	"github.com/docker/docker/reference"
907407d0
 	"github.com/docker/engine-api/types"
 	registrytypes "github.com/docker/engine-api/types/registry"
73823e5e
 )
bb9da6ba
 
92f10fe2
 const (
 	// DefaultSearchLimit is the default value for maximum number of returned search results.
 	DefaultSearchLimit = 25
 )
 
636c276f
 // Service is the interface defining what a registry service should implement.
 type Service interface {
 	Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
 	LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
 	LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
 	ResolveRepository(name reference.Named) (*RepositoryInfo, error)
 	ResolveIndex(name string) (*registrytypes.IndexInfo, error)
92f10fe2
 	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
636c276f
 	ServiceConfig() *registrytypes.ServiceConfig
 	TLSConfig(hostname string) (*tls.Config, error)
 }
 
 // DefaultService is a registry service. It tracks configuration data such as a list
4fcb9ac4
 // of mirrors.
636c276f
 type DefaultService struct {
59586d02
 	config *serviceConfig
3d605683
 }
 
636c276f
 // NewService returns a new instance of DefaultService ready to be
4fcb9ac4
 // installed into an engine.
636c276f
 func NewService(options ServiceOptions) *DefaultService {
 	return &DefaultService{
59586d02
 		config: newServiceConfig(options),
afade423
 	}
3d605683
 }
 
59586d02
 // ServiceConfig returns the public registry service configuration.
636c276f
 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
59586d02
 	return &s.config.ServiceConfig
 }
 
3d605683
 // Auth contacts the public registry with the provided credentials,
51462327
 // and returns OK if authentication was successful.
3d605683
 // It can be used to verify the validity of a client's credentials.
636c276f
 func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
 	// TODO Use ctx when searching for repositories
e896d1d7
 	serverAddress := authConfig.ServerAddress
93973196
 	if serverAddress == "" {
 		serverAddress = IndexServer
 	}
e896d1d7
 	if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
 		serverAddress = "https://" + serverAddress
 	}
 	u, err := url.Parse(serverAddress)
 	if err != nil {
 		return "", "", fmt.Errorf("unable to parse server address: %v", err)
 	}
 
 	endpoints, err := s.LookupPushEndpoints(u.Host)
03d3d79b
 	if err != nil {
e896d1d7
 		return "", "", err
3d605683
 	}
39f2f15a
 
f2d481a2
 	for _, endpoint := range endpoints {
 		login := loginV2
 		if endpoint.Version == APIVersion1 {
 			login = loginV1
 		}
39f2f15a
 
e896d1d7
 		status, token, err = login(authConfig, endpoint, userAgent)
f2d481a2
 		if err == nil {
 			return
 		}
 		if fErr, ok := err.(fallbackError); ok {
 			err = fErr.err
 			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
 			continue
 		}
e896d1d7
 		return "", "", err
41e20cec
 	}
f2d481a2
 
e896d1d7
 	return "", "", err
3d605683
 }
c4089ad8
 
4352da78
 // splitReposSearchTerm breaks a search term into an index name and remote name
 func splitReposSearchTerm(reposName string) (string, string) {
 	nameParts := strings.SplitN(reposName, "/", 2)
 	var indexName, remoteName string
 	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
 		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
 		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
 		// 'docker.io'
 		indexName = IndexName
 		remoteName = reposName
 	} else {
 		indexName = nameParts[0]
 		remoteName = nameParts[1]
 	}
 	return indexName, remoteName
 }
 
c4089ad8
 // Search queries the public registry for images matching the specified
 // search terms, and returns the results.
92f10fe2
 func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
636c276f
 	// TODO Use ctx when searching for repositories
28d3c22e
 	if err := validateNoScheme(term); err != nil {
4352da78
 		return nil, err
 	}
 
 	indexName, remoteName := splitReposSearchTerm(term)
f04e8fdb
 
59586d02
 	index, err := newIndexInfo(s.config, indexName)
3231033a
 	if err != nil {
03d3d79b
 		return nil, err
3231033a
 	}
a01cc3ca
 
568f86eb
 	// *TODO: Search multiple indexes.
f2d481a2
 	endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
3231033a
 	if err != nil {
03d3d79b
 		return nil, err
c4089ad8
 	}
39f2f15a
 
e678d9fb
 	var client *http.Client
 	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
 		creds := NewStaticCredentialStore(authConfig)
 		scopes := []auth.Scope{
 			auth.RegistryScope{
 				Name:    "catalog",
 				Actions: []string{"search"},
 			},
 		}
 
 		modifiers := DockerHeaders(userAgent, nil)
 		v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
 		if err != nil {
 			if fErr, ok := err.(fallbackError); ok {
 				logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
 			} else {
 				return nil, err
 			}
 		} else if foundV2 {
 			// Copy non transport http client features
 			v2Client.Timeout = endpoint.client.Timeout
 			v2Client.CheckRedirect = endpoint.client.CheckRedirect
 			v2Client.Jar = endpoint.client.Jar
 
 			logrus.Debugf("using v2 client for search to %s", endpoint.URL)
 			client = v2Client
 		}
c4089ad8
 	}
568f86eb
 
e678d9fb
 	if client == nil {
 		client = endpoint.client
 		if err := authorizeClient(client, authConfig, endpoint); err != nil {
 			return nil, err
 		}
 	}
 
 	r := newSession(client, authConfig, endpoint)
 
4352da78
 	if index.Official {
 		localName := remoteName
 		if strings.HasPrefix(localName, "library/") {
 			// If pull "library/foo", it's stored locally under "foo"
 			localName = strings.SplitN(localName, "/", 2)[1]
 		}
 
92f10fe2
 		return r.SearchRepositories(localName, limit)
4352da78
 	}
92f10fe2
 	return r.SearchRepositories(remoteName, limit)
f04e8fdb
 }
 
4352da78
 // ResolveRepository splits a repository name into its components
f04e8fdb
 // and configuration of the associated registry.
636c276f
 func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
59586d02
 	return newRepositoryInfo(s.config, name)
568f86eb
 }
 
 // ResolveIndex takes indexName and returns index info
636c276f
 func (s *DefaultService) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
59586d02
 	return newIndexInfo(s.config, name)
568f86eb
 }
19515a7a
 
4fcb9ac4
 // APIEndpoint represents a remote API endpoint
19515a7a
 type APIEndpoint struct {
a57478d6
 	Mirror       bool
79db131a
 	URL          *url.URL
a57478d6
 	Version      APIVersion
 	Official     bool
 	TrimHostname bool
 	TLSConfig    *tls.Config
19515a7a
 }
 
4fcb9ac4
 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
f2d481a2
 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
 	return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
19515a7a
 }
 
4fcb9ac4
 // TLSConfig constructs a client TLS configuration based on server defaults
636c276f
 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
59586d02
 	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
19515a7a
 }
 
636c276f
 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
4fcb9ac4
 	return s.TLSConfig(mirrorURL.Host)
cb57b256
 }
 
34b82a69
 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
4fcb9ac4
 // It gives preference to v2 endpoints over v1, mirrors over the actual
 // registry, and HTTPS over plain HTTP.
636c276f
 func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
f2d481a2
 	return s.lookupEndpoints(hostname)
b899977e
 }
 
34b82a69
 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
b899977e
 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
 // Mirrors are not included.
636c276f
 func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
f2d481a2
 	allEndpoints, err := s.lookupEndpoints(hostname)
c016d2de
 	if err == nil {
 		for _, endpoint := range allEndpoints {
 			if !endpoint.Mirror {
 				endpoints = append(endpoints, endpoint)
 			}
 		}
 	}
 	return endpoints, err
b899977e
 }
 
636c276f
 func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
f2d481a2
 	endpoints, err = s.lookupV2Endpoints(hostname)
19515a7a
 	if err != nil {
 		return nil, err
 	}
 
59586d02
 	if s.config.V2Only {
39f2f15a
 		return endpoints, nil
19515a7a
 	}
 
f2d481a2
 	legacyEndpoints, err := s.lookupV1Endpoints(hostname)
39f2f15a
 	if err != nil {
 		return nil, err
19515a7a
 	}
39f2f15a
 	endpoints = append(endpoints, legacyEndpoints...)
19515a7a
 
 	return endpoints, nil
 }