github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/registry/service.go (about)

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