Tizen_4.0 base
[platform/upstream/docker-engine.git] / registry / service.go
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         LoadAllowNondistributableArtifacts([]string) error
35         LoadMirrors([]string) error
36         LoadInsecureRegistries([]string) error
37 }
38
39 // DefaultService is a registry service. It tracks configuration data such as a list
40 // of mirrors.
41 type DefaultService struct {
42         config *serviceConfig
43         mu     sync.Mutex
44 }
45
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),
51         }
52 }
53
54 // ServiceConfig returns the public registry service configuration.
55 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
56         s.mu.Lock()
57         defer s.mu.Unlock()
58
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),
65         }
66
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...)
72
73         for key, value := range s.config.ServiceConfig.IndexConfigs {
74                 servConfig.IndexConfigs[key] = value
75         }
76
77         servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
78
79         return &servConfig
80 }
81
82 // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
83 func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
84         s.mu.Lock()
85         defer s.mu.Unlock()
86
87         return s.config.LoadAllowNondistributableArtifacts(registries)
88 }
89
90 // LoadMirrors loads registry mirrors for Service
91 func (s *DefaultService) LoadMirrors(mirrors []string) error {
92         s.mu.Lock()
93         defer s.mu.Unlock()
94
95         return s.config.LoadMirrors(mirrors)
96 }
97
98 // LoadInsecureRegistries loads insecure registries for Service
99 func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
100         s.mu.Lock()
101         defer s.mu.Unlock()
102
103         return s.config.LoadInsecureRegistries(registries)
104 }
105
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
114         }
115         if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
116                 serverAddress = "https://" + serverAddress
117         }
118         u, err := url.Parse(serverAddress)
119         if err != nil {
120                 return "", "", fmt.Errorf("unable to parse server address: %v", err)
121         }
122
123         endpoints, err := s.LookupPushEndpoints(u.Host)
124         if err != nil {
125                 return "", "", err
126         }
127
128         for _, endpoint := range endpoints {
129                 login := loginV2
130                 if endpoint.Version == APIVersion1 {
131                         login = loginV1
132                 }
133
134                 status, token, err = login(authConfig, endpoint, userAgent)
135                 if err == nil {
136                         return
137                 }
138                 if fErr, ok := err.(fallbackError); ok {
139                         err = fErr.err
140                         logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
141                         continue
142                 }
143                 return "", "", err
144         }
145
146         return "", "", err
147 }
148
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)
156                 // 'docker.io'
157                 indexName = IndexName
158                 remoteName = reposName
159         } else {
160                 indexName = nameParts[0]
161                 remoteName = nameParts[1]
162         }
163         return indexName, remoteName
164 }
165
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 {
171                 return nil, err
172         }
173
174         indexName, remoteName := splitReposSearchTerm(term)
175
176         // Search is a long-running operation, just lock s.config to avoid block others.
177         s.mu.Lock()
178         index, err := newIndexInfo(s.config, indexName)
179         s.mu.Unlock()
180
181         if err != nil {
182                 return nil, err
183         }
184
185         // *TODO: Search multiple indexes.
186         endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
187         if err != nil {
188                 return nil, err
189         }
190
191         var client *http.Client
192         if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
193                 creds := NewStaticCredentialStore(authConfig)
194                 scopes := []auth.Scope{
195                         auth.RegistryScope{
196                                 Name:    "catalog",
197                                 Actions: []string{"search"},
198                         },
199                 }
200
201                 modifiers := DockerHeaders(userAgent, nil)
202                 v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
203                 if err != nil {
204                         if fErr, ok := err.(fallbackError); ok {
205                                 logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
206                         } else {
207                                 return nil, err
208                         }
209                 } else if foundV2 {
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
214
215                         logrus.Debugf("using v2 client for search to %s", endpoint.URL)
216                         client = v2Client
217                 }
218         }
219
220         if client == nil {
221                 client = endpoint.client
222                 if err := authorizeClient(client, authConfig, endpoint); err != nil {
223                         return nil, err
224                 }
225         }
226
227         r := newSession(client, authConfig, endpoint)
228
229         if index.Official {
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]
234                 }
235
236                 return r.SearchRepositories(localName, limit)
237         }
238         return r.SearchRepositories(remoteName, limit)
239 }
240
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) {
244         s.mu.Lock()
245         defer s.mu.Unlock()
246         return newRepositoryInfo(s.config, name)
247 }
248
249 // APIEndpoint represents a remote API endpoint
250 type APIEndpoint struct {
251         Mirror                         bool
252         URL                            *url.URL
253         Version                        APIVersion
254         AllowNondistributableArtifacts bool
255         Official                       bool
256         TrimHostname                   bool
257         TLSConfig                      *tls.Config
258 }
259
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)
263 }
264
265 // TLSConfig constructs a client TLS configuration based on server defaults
266 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
267         s.mu.Lock()
268         defer s.mu.Unlock()
269
270         return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
271 }
272
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))
276 }
277
278 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
279         return s.tlsConfig(mirrorURL.Host)
280 }
281
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) {
286         s.mu.Lock()
287         defer s.mu.Unlock()
288
289         return s.lookupEndpoints(hostname)
290 }
291
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) {
296         s.mu.Lock()
297         defer s.mu.Unlock()
298
299         allEndpoints, err := s.lookupEndpoints(hostname)
300         if err == nil {
301                 for _, endpoint := range allEndpoints {
302                         if !endpoint.Mirror {
303                                 endpoints = append(endpoints, endpoint)
304                         }
305                 }
306         }
307         return endpoints, err
308 }
309
310 func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
311         endpoints, err = s.lookupV2Endpoints(hostname)
312         if err != nil {
313                 return nil, err
314         }
315
316         if s.config.V2Only {
317                 return endpoints, nil
318         }
319
320         legacyEndpoints, err := s.lookupV1Endpoints(hostname)
321         if err != nil {
322                 return nil, err
323         }
324         endpoints = append(endpoints, legacyEndpoints...)
325
326         return endpoints, nil
327 }