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 }