11 "golang.org/x/net/context"
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"
21 // DefaultSearchLimit is the default value for maximum number of returned search results.
22 DefaultSearchLimit = 25
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 LoadAllowNondistributableArtifacts([]string) error
35 LoadMirrors([]string) error
36 LoadInsecureRegistries([]string) error
39 // DefaultService is a registry service. It tracks configuration data such as a list
41 type DefaultService struct {
46 // NewService returns a new instance of DefaultService ready to be
47 // installed into an engine.
48 func NewService(options ServiceOptions) *DefaultService {
49 return &DefaultService{
50 config: newServiceConfig(options),
54 // ServiceConfig returns the public registry service configuration.
55 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
59 servConfig := registrytypes.ServiceConfig{
60 AllowNondistributableArtifactsCIDRs: make([]*(registrytypes.NetIPNet), 0),
61 AllowNondistributableArtifactsHostnames: make([]string, 0),
62 InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
63 IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
64 Mirrors: make([]string, 0),
67 // construct a new ServiceConfig which will not retrieve s.Config directly,
68 // and look up items in s.config with mu locked
69 servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
70 servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
71 servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
73 for key, value := range s.config.ServiceConfig.IndexConfigs {
74 servConfig.IndexConfigs[key] = value
77 servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
82 // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
83 func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
87 return s.config.LoadAllowNondistributableArtifacts(registries)
90 // LoadMirrors loads registry mirrors for Service
91 func (s *DefaultService) LoadMirrors(mirrors []string) error {
95 return s.config.LoadMirrors(mirrors)
98 // LoadInsecureRegistries loads insecure registries for Service
99 func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
103 return s.config.LoadInsecureRegistries(registries)
106 // Auth contacts the public registry with the provided credentials,
107 // and returns OK if authentication was successful.
108 // It can be used to verify the validity of a client's credentials.
109 func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
110 // TODO Use ctx when searching for repositories
111 serverAddress := authConfig.ServerAddress
112 if serverAddress == "" {
113 serverAddress = IndexServer
115 if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
116 serverAddress = "https://" + serverAddress
118 u, err := url.Parse(serverAddress)
120 return "", "", fmt.Errorf("unable to parse server address: %v", err)
123 endpoints, err := s.LookupPushEndpoints(u.Host)
128 for _, endpoint := range endpoints {
130 if endpoint.Version == APIVersion1 {
134 status, token, err = login(authConfig, endpoint, userAgent)
138 if fErr, ok := err.(fallbackError); ok {
140 logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
149 // splitReposSearchTerm breaks a search term into an index name and remote name
150 func splitReposSearchTerm(reposName string) (string, string) {
151 nameParts := strings.SplitN(reposName, "/", 2)
152 var indexName, remoteName string
153 if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
154 !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
155 // This is a Docker Index repos (ex: samalba/hipache or ubuntu)
157 indexName = IndexName
158 remoteName = reposName
160 indexName = nameParts[0]
161 remoteName = nameParts[1]
163 return indexName, remoteName
166 // Search queries the public registry for images matching the specified
167 // search terms, and returns the results.
168 func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
169 // TODO Use ctx when searching for repositories
170 if err := validateNoScheme(term); err != nil {
174 indexName, remoteName := splitReposSearchTerm(term)
176 // Search is a long-running operation, just lock s.config to avoid block others.
178 index, err := newIndexInfo(s.config, indexName)
185 // *TODO: Search multiple indexes.
186 endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
191 var client *http.Client
192 if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
193 creds := NewStaticCredentialStore(authConfig)
194 scopes := []auth.Scope{
197 Actions: []string{"search"},
201 modifiers := DockerHeaders(userAgent, nil)
202 v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
204 if fErr, ok := err.(fallbackError); ok {
205 logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
210 // Copy non transport http client features
211 v2Client.Timeout = endpoint.client.Timeout
212 v2Client.CheckRedirect = endpoint.client.CheckRedirect
213 v2Client.Jar = endpoint.client.Jar
215 logrus.Debugf("using v2 client for search to %s", endpoint.URL)
221 client = endpoint.client
222 if err := authorizeClient(client, authConfig, endpoint); err != nil {
227 r := newSession(client, authConfig, endpoint)
230 localName := remoteName
231 if strings.HasPrefix(localName, "library/") {
232 // If pull "library/foo", it's stored locally under "foo"
233 localName = strings.SplitN(localName, "/", 2)[1]
236 return r.SearchRepositories(localName, limit)
238 return r.SearchRepositories(remoteName, limit)
241 // ResolveRepository splits a repository name into its components
242 // and configuration of the associated registry.
243 func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
246 return newRepositoryInfo(s.config, name)
249 // APIEndpoint represents a remote API endpoint
250 type APIEndpoint struct {
254 AllowNondistributableArtifacts bool
257 TLSConfig *tls.Config
260 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
261 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) {
262 return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
265 // TLSConfig constructs a client TLS configuration based on server defaults
266 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
270 return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
273 // tlsConfig constructs a client TLS configuration based on server defaults
274 func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
275 return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
278 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
279 return s.tlsConfig(mirrorURL.Host)
282 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
283 // It gives preference to v2 endpoints over v1, mirrors over the actual
284 // registry, and HTTPS over plain HTTP.
285 func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
289 return s.lookupEndpoints(hostname)
292 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
293 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
294 // Mirrors are not included.
295 func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
299 allEndpoints, err := s.lookupEndpoints(hostname)
301 for _, endpoint := range allEndpoints {
302 if !endpoint.Mirror {
303 endpoints = append(endpoints, endpoint)
307 return endpoints, err
310 func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
311 endpoints, err = s.lookupV2Endpoints(hostname)
317 return endpoints, nil
320 legacyEndpoints, err := s.lookupV1Endpoints(hostname)
324 endpoints = append(endpoints, legacyEndpoints...)
326 return endpoints, nil