3d605683 |
package registry
|
73823e5e |
import ( |
19515a7a |
"crypto/tls" |
e896d1d7 |
"fmt" |
73823e5e |
"net/http" |
cb57b256 |
"net/url" |
4352da78 |
"strings" |
73823e5e |
|
636c276f |
"golang.org/x/net/context"
|
f2d481a2 |
"github.com/Sirupsen/logrus" |
e678d9fb |
"github.com/docker/distribution/registry/client/auth" |
2655954c |
"github.com/docker/docker/reference" |
907407d0 |
"github.com/docker/engine-api/types"
registrytypes "github.com/docker/engine-api/types/registry" |
73823e5e |
) |
bb9da6ba |
|
92f10fe2 |
const (
// DefaultSearchLimit is the default value for maximum number of returned search results.
DefaultSearchLimit = 25
)
|
636c276f |
// Service is the interface defining what a registry service should implement.
type Service interface {
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
ResolveRepository(name reference.Named) (*RepositoryInfo, error)
ResolveIndex(name string) (*registrytypes.IndexInfo, error) |
92f10fe2 |
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) |
636c276f |
ServiceConfig() *registrytypes.ServiceConfig
TLSConfig(hostname string) (*tls.Config, error)
}
// DefaultService is a registry service. It tracks configuration data such as a list |
4fcb9ac4 |
// of mirrors. |
636c276f |
type DefaultService struct { |
59586d02 |
config *serviceConfig |
3d605683 |
}
|
636c276f |
// NewService returns a new instance of DefaultService ready to be |
4fcb9ac4 |
// installed into an engine. |
636c276f |
func NewService(options ServiceOptions) *DefaultService {
return &DefaultService{ |
59586d02 |
config: newServiceConfig(options), |
afade423 |
} |
3d605683 |
}
|
59586d02 |
// ServiceConfig returns the public registry service configuration. |
636c276f |
func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig { |
59586d02 |
return &s.config.ServiceConfig
}
|
3d605683 |
// Auth contacts the public registry with the provided credentials, |
51462327 |
// and returns OK if authentication was successful. |
3d605683 |
// It can be used to verify the validity of a client's credentials. |
636c276f |
func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
// TODO Use ctx when searching for repositories |
e896d1d7 |
serverAddress := authConfig.ServerAddress |
93973196 |
if serverAddress == "" {
serverAddress = IndexServer
} |
e896d1d7 |
if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
serverAddress = "https://" + serverAddress
}
u, err := url.Parse(serverAddress)
if err != nil {
return "", "", fmt.Errorf("unable to parse server address: %v", err)
}
endpoints, err := s.LookupPushEndpoints(u.Host) |
03d3d79b |
if err != nil { |
e896d1d7 |
return "", "", err |
3d605683 |
} |
39f2f15a |
|
f2d481a2 |
for _, endpoint := range endpoints {
login := loginV2
if endpoint.Version == APIVersion1 {
login = loginV1
} |
39f2f15a |
|
e896d1d7 |
status, token, err = login(authConfig, endpoint, userAgent) |
f2d481a2 |
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
} |
e896d1d7 |
return "", "", err |
41e20cec |
} |
f2d481a2 |
|
e896d1d7 |
return "", "", err |
3d605683 |
} |
c4089ad8 |
|
4352da78 |
// 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
}
|
c4089ad8 |
// Search queries the public registry for images matching the specified
// search terms, and returns the results. |
92f10fe2 |
func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) { |
636c276f |
// TODO Use ctx when searching for repositories |
28d3c22e |
if err := validateNoScheme(term); err != nil { |
4352da78 |
return nil, err
}
indexName, remoteName := splitReposSearchTerm(term) |
f04e8fdb |
|
59586d02 |
index, err := newIndexInfo(s.config, indexName) |
3231033a |
if err != nil { |
03d3d79b |
return nil, err |
3231033a |
} |
a01cc3ca |
|
568f86eb |
// *TODO: Search multiple indexes. |
f2d481a2 |
endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers)) |
3231033a |
if err != nil { |
03d3d79b |
return nil, err |
c4089ad8 |
} |
39f2f15a |
|
e678d9fb |
var client *http.Client
if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
creds := NewStaticCredentialStore(authConfig)
scopes := []auth.Scope{
auth.RegistryScope{
Name: "catalog",
Actions: []string{"search"},
},
}
modifiers := DockerHeaders(userAgent, nil)
v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
if err != nil {
if fErr, ok := err.(fallbackError); ok {
logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
} else {
return nil, err
}
} else if foundV2 {
// Copy non transport http client features
v2Client.Timeout = endpoint.client.Timeout
v2Client.CheckRedirect = endpoint.client.CheckRedirect
v2Client.Jar = endpoint.client.Jar
logrus.Debugf("using v2 client for search to %s", endpoint.URL)
client = v2Client
} |
c4089ad8 |
} |
568f86eb |
|
e678d9fb |
if client == nil {
client = endpoint.client
if err := authorizeClient(client, authConfig, endpoint); err != nil {
return nil, err
}
}
r := newSession(client, authConfig, endpoint)
|
4352da78 |
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]
}
|
92f10fe2 |
return r.SearchRepositories(localName, limit) |
4352da78 |
} |
92f10fe2 |
return r.SearchRepositories(remoteName, limit) |
f04e8fdb |
}
|
4352da78 |
// ResolveRepository splits a repository name into its components |
f04e8fdb |
// and configuration of the associated registry. |
636c276f |
func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { |
59586d02 |
return newRepositoryInfo(s.config, name) |
568f86eb |
}
// ResolveIndex takes indexName and returns index info |
636c276f |
func (s *DefaultService) ResolveIndex(name string) (*registrytypes.IndexInfo, error) { |
59586d02 |
return newIndexInfo(s.config, name) |
568f86eb |
} |
19515a7a |
|
4fcb9ac4 |
// APIEndpoint represents a remote API endpoint |
19515a7a |
type APIEndpoint struct { |
a57478d6 |
Mirror bool |
79db131a |
URL *url.URL |
a57478d6 |
Version APIVersion
Official bool
TrimHostname bool
TLSConfig *tls.Config |
19515a7a |
}
|
4fcb9ac4 |
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint |
f2d481a2 |
func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders) |
19515a7a |
}
|
4fcb9ac4 |
// TLSConfig constructs a client TLS configuration based on server defaults |
636c276f |
func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) { |
59586d02 |
return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) |
19515a7a |
}
|
636c276f |
func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) { |
4fcb9ac4 |
return s.TLSConfig(mirrorURL.Host) |
cb57b256 |
}
|
34b82a69 |
// LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference. |
4fcb9ac4 |
// It gives preference to v2 endpoints over v1, mirrors over the actual
// registry, and HTTPS over plain HTTP. |
636c276f |
func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) { |
f2d481a2 |
return s.lookupEndpoints(hostname) |
b899977e |
}
|
34b82a69 |
// LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference. |
b899977e |
// It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
// Mirrors are not included. |
636c276f |
func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) { |
f2d481a2 |
allEndpoints, err := s.lookupEndpoints(hostname) |
c016d2de |
if err == nil {
for _, endpoint := range allEndpoints {
if !endpoint.Mirror {
endpoints = append(endpoints, endpoint)
}
}
}
return endpoints, err |
b899977e |
}
|
636c276f |
func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) { |
f2d481a2 |
endpoints, err = s.lookupV2Endpoints(hostname) |
19515a7a |
if err != nil {
return nil, err
}
|
59586d02 |
if s.config.V2Only { |
39f2f15a |
return endpoints, nil |
19515a7a |
}
|
f2d481a2 |
legacyEndpoints, err := s.lookupV1Endpoints(hostname) |
39f2f15a |
if err != nil {
return nil, err |
19515a7a |
} |
39f2f15a |
endpoints = append(endpoints, legacyEndpoints...) |
19515a7a |
return endpoints, nil
} |