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