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