forked from TrueCloudLab/distribution
065ddf0186
Further differentiate the APIEndpoint used with V2 with the endpoint type which is only used for v1 registry interactions Rename Endpoint to V1Endpoint and remove version ambiguity Use distribution token handler for login Signed-off-by: Derek McGowan <derek@mcgstyle.net> Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
187 lines
5.3 KiB
Go
187 lines
5.3 KiB
Go
package registry
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/docker/reference"
|
|
"github.com/docker/engine-api/types"
|
|
registrytypes "github.com/docker/engine-api/types/registry"
|
|
)
|
|
|
|
// Service is a registry service. It tracks configuration data such as a list
|
|
// of mirrors.
|
|
type Service struct {
|
|
Config *registrytypes.ServiceConfig
|
|
}
|
|
|
|
// NewService returns a new instance of Service ready to be
|
|
// installed into an engine.
|
|
func NewService(options *Options) *Service {
|
|
return &Service{
|
|
Config: NewServiceConfig(options),
|
|
}
|
|
}
|
|
|
|
// Auth contacts the public registry with the provided credentials,
|
|
// and returns OK if authentication was successful.
|
|
// It can be used to verify the validity of a client's credentials.
|
|
func (s *Service) Auth(authConfig *types.AuthConfig, userAgent string) (status string, err error) {
|
|
endpoints, err := s.LookupPushEndpoints(authConfig.ServerAddress)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
for _, endpoint := range endpoints {
|
|
login := loginV2
|
|
if endpoint.Version == APIVersion1 {
|
|
login = loginV1
|
|
}
|
|
|
|
status, err = login(authConfig, endpoint, userAgent)
|
|
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
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Search queries the public registry for images matching the specified
|
|
// search terms, and returns the results.
|
|
func (s *Service) Search(term string, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
|
|
if err := validateNoSchema(term); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
indexName, remoteName := splitReposSearchTerm(term)
|
|
|
|
index, err := newIndexInfo(s.Config, indexName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// *TODO: Search multiple indexes.
|
|
endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err := NewSession(endpoint.client, authConfig, endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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]
|
|
}
|
|
|
|
return r.SearchRepositories(localName)
|
|
}
|
|
return r.SearchRepositories(remoteName)
|
|
}
|
|
|
|
// ResolveRepository splits a repository name into its components
|
|
// and configuration of the associated registry.
|
|
func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
|
return newRepositoryInfo(s.Config, name)
|
|
}
|
|
|
|
// ResolveIndex takes indexName and returns index info
|
|
func (s *Service) ResolveIndex(name string) (*registrytypes.IndexInfo, error) {
|
|
return newIndexInfo(s.Config, name)
|
|
}
|
|
|
|
// APIEndpoint represents a remote API endpoint
|
|
type APIEndpoint struct {
|
|
Mirror bool
|
|
URL *url.URL
|
|
Version APIVersion
|
|
Official bool
|
|
TrimHostname bool
|
|
TLSConfig *tls.Config
|
|
}
|
|
|
|
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
|
|
func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
|
|
return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
|
|
}
|
|
|
|
// TLSConfig constructs a client TLS configuration based on server defaults
|
|
func (s *Service) TLSConfig(hostname string) (*tls.Config, error) {
|
|
return newTLSConfig(hostname, isSecureIndex(s.Config, hostname))
|
|
}
|
|
|
|
func (s *Service) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
|
|
return s.TLSConfig(mirrorURL.Host)
|
|
}
|
|
|
|
// LookupPullEndpoints creates an list of endpoints to try to pull from, in order of preference.
|
|
// It gives preference to v2 endpoints over v1, mirrors over the actual
|
|
// registry, and HTTPS over plain HTTP.
|
|
func (s *Service) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
return s.lookupEndpoints(hostname)
|
|
}
|
|
|
|
// LookupPushEndpoints creates an list of endpoints to try to push to, in order of preference.
|
|
// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
|
|
// Mirrors are not included.
|
|
func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
allEndpoints, err := s.lookupEndpoints(hostname)
|
|
if err == nil {
|
|
for _, endpoint := range allEndpoints {
|
|
if !endpoint.Mirror {
|
|
endpoints = append(endpoints, endpoint)
|
|
}
|
|
}
|
|
}
|
|
return endpoints, err
|
|
}
|
|
|
|
func (s *Service) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
|
endpoints, err = s.lookupV2Endpoints(hostname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if V2Only {
|
|
return endpoints, nil
|
|
}
|
|
|
|
legacyEndpoints, err := s.lookupV1Endpoints(hostname)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endpoints = append(endpoints, legacyEndpoints...)
|
|
|
|
return endpoints, nil
|
|
}
|