github.com/portworx/docker@v1.12.1/registry/service.go (about)

     1  package registry
     2  
     3  import (
     4  	"crypto/tls"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"golang.org/x/net/context"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/distribution/registry/client/auth"
    14  	"github.com/docker/docker/reference"
    15  	"github.com/docker/engine-api/types"
    16  	registrytypes "github.com/docker/engine-api/types/registry"
    17  )
    18  
    19  const (
    20  	// DefaultSearchLimit is the default value for maximum number of returned search results.
    21  	DefaultSearchLimit = 25
    22  )
    23  
    24  // Service is the interface defining what a registry service should implement.
    25  type Service interface {
    26  	Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
    27  	LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
    28  	LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
    29  	ResolveRepository(name reference.Named) (*RepositoryInfo, error)
    30  	ResolveIndex(name string) (*registrytypes.IndexInfo, error)
    31  	Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
    32  	ServiceConfig() *registrytypes.ServiceConfig
    33  	TLSConfig(hostname string) (*tls.Config, error)
    34  }
    35  
    36  // DefaultService is a registry service. It tracks configuration data such as a list
    37  // of mirrors.
    38  type DefaultService struct {
    39  	config *serviceConfig
    40  }
    41  
    42  // NewService returns a new instance of DefaultService ready to be
    43  // installed into an engine.
    44  func NewService(options ServiceOptions) *DefaultService {
    45  	return &DefaultService{
    46  		config: newServiceConfig(options),
    47  	}
    48  }
    49  
    50  // ServiceConfig returns the public registry service configuration.
    51  func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
    52  	return &s.config.ServiceConfig
    53  }
    54  
    55  // Auth contacts the public registry with the provided credentials,
    56  // and returns OK if authentication was successful.
    57  // It can be used to verify the validity of a client's credentials.
    58  func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
    59  	// TODO Use ctx when searching for repositories
    60  	serverAddress := authConfig.ServerAddress
    61  	if serverAddress == "" {
    62  		serverAddress = IndexServer
    63  	}
    64  	if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
    65  		serverAddress = "https://" + serverAddress
    66  	}
    67  	u, err := url.Parse(serverAddress)
    68  	if err != nil {
    69  		return "", "", fmt.Errorf("unable to parse server address: %v", err)
    70  	}
    71  
    72  	endpoints, err := s.LookupPushEndpoints(u.Host)
    73  	if err != nil {
    74  		return "", "", err
    75  	}
    76  
    77  	for _, endpoint := range endpoints {
    78  		login := loginV2
    79  		if endpoint.Version == APIVersion1 {
    80  			login = loginV1
    81  		}
    82  
    83  		status, token, err = login(authConfig, endpoint, userAgent)
    84  		if err == nil {
    85  			return
    86  		}
    87  		if fErr, ok := err.(fallbackError); ok {
    88  			err = fErr.err
    89  			logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
    90  			continue
    91  		}
    92  		return "", "", err
    93  	}
    94  
    95  	return "", "", err
    96  }
    97  
    98  // splitReposSearchTerm breaks a search term into an index name and remote name
    99  func splitReposSearchTerm(reposName string) (string, string) {
   100  	nameParts := strings.SplitN(reposName, "/", 2)
   101  	var indexName, remoteName string
   102  	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
   103  		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
   104  		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
   105  		// 'docker.io'
   106  		indexName = IndexName
   107  		remoteName = reposName
   108  	} else {
   109  		indexName = nameParts[0]
   110  		remoteName = nameParts[1]
   111  	}
   112  	return indexName, remoteName
   113  }
   114  
   115  // Search queries the public registry for images matching the specified
   116  // search terms, and returns the results.
   117  func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
   118  	// TODO Use ctx when searching for repositories
   119  	if err := validateNoScheme(term); err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	indexName, remoteName := splitReposSearchTerm(term)
   124  
   125  	index, err := newIndexInfo(s.config, indexName)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	// *TODO: Search multiple indexes.
   131  	endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	var client *http.Client
   137  	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
   138  		creds := NewStaticCredentialStore(authConfig)
   139  		scopes := []auth.Scope{
   140  			auth.RegistryScope{
   141  				Name:    "catalog",
   142  				Actions: []string{"search"},
   143  			},
   144  		}
   145  
   146  		modifiers := DockerHeaders(userAgent, nil)
   147  		v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
   148  		if err != nil {
   149  			if fErr, ok := err.(fallbackError); ok {
   150  				logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
   151  			} else {
   152  				return nil, err
   153  			}
   154  		} else if foundV2 {
   155  			// Copy non transport http client features
   156  			v2Client.Timeout = endpoint.client.Timeout
   157  			v2Client.CheckRedirect = endpoint.client.CheckRedirect
   158  			v2Client.Jar = endpoint.client.Jar
   159  
   160  			logrus.Debugf("using v2 client for search to %s", endpoint.URL)
   161  			client = v2Client
   162  		}
   163  	}
   164  
   165  	if client == nil {
   166  		client = endpoint.client
   167  		if err := authorizeClient(client, authConfig, endpoint); err != nil {
   168  			return nil, err
   169  		}
   170  	}
   171  
   172  	r := newSession(client, authConfig, endpoint)
   173  
   174  	if index.Official {
   175  		localName := remoteName
   176  		if strings.HasPrefix(localName, "library/") {
   177  			// If pull "library/foo", it's stored locally under "foo"
   178  			localName = strings.SplitN(localName, "/", 2)[1]
   179  		}
   180  
   181  		return r.SearchRepositories(localName, limit)
   182  	}
   183  	return r.SearchRepositories(remoteName, limit)
   184  }
   185  
   186  // ResolveRepository splits a repository name into its components
   187  // and configuration of the associated registry.
   188  func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
   189  	return newRepositoryInfo(s.config, name)
   190  }
   191  
   192  // ResolveIndex takes indexName and returns index info
   193  func (s *DefaultService) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
   194  	return newIndexInfo(s.config, name)
   195  }
   196  
   197  // APIEndpoint represents a remote API endpoint
   198  type APIEndpoint struct {
   199  	Mirror       bool
   200  	URL          *url.URL
   201  	Version      APIVersion
   202  	Official     bool
   203  	TrimHostname bool
   204  	TLSConfig    *tls.Config
   205  }
   206  
   207  // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
   208  func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
   209  	return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
   210  }
   211  
   212  // TLSConfig constructs a client TLS configuration based on server defaults
   213  func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
   214  	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
   215  }
   216  
   217  func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
   218  	return s.TLSConfig(mirrorURL.Host)
   219  }
   220  
   221  // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
   222  // It gives preference to v2 endpoints over v1, mirrors over the actual
   223  // registry, and HTTPS over plain HTTP.
   224  func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
   225  	return s.lookupEndpoints(hostname)
   226  }
   227  
   228  // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
   229  // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
   230  // Mirrors are not included.
   231  func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
   232  	allEndpoints, err := s.lookupEndpoints(hostname)
   233  	if err == nil {
   234  		for _, endpoint := range allEndpoints {
   235  			if !endpoint.Mirror {
   236  				endpoints = append(endpoints, endpoint)
   237  			}
   238  		}
   239  	}
   240  	return endpoints, err
   241  }
   242  
   243  func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
   244  	endpoints, err = s.lookupV2Endpoints(hostname)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	if s.config.V2Only {
   250  		return endpoints, nil
   251  	}
   252  
   253  	legacyEndpoints, err := s.lookupV1Endpoints(hostname)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	endpoints = append(endpoints, legacyEndpoints...)
   258  
   259  	return endpoints, nil
   260  }