github.com/duglin/docker@v1.13.1/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/registry/client/auth" 15 "github.com/docker/docker/api/types" 16 registrytypes "github.com/docker/docker/api/types/registry" 17 "github.com/docker/docker/reference" 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 LoadInsecureRegistries([]string) error 35 } 36 37 // DefaultService is a registry service. It tracks configuration data such as a list 38 // of mirrors. 39 type DefaultService struct { 40 config *serviceConfig 41 mu sync.Mutex 42 } 43 44 // NewService returns a new instance of DefaultService ready to be 45 // installed into an engine. 46 func NewService(options ServiceOptions) *DefaultService { 47 return &DefaultService{ 48 config: newServiceConfig(options), 49 } 50 } 51 52 // ServiceConfig returns the public registry service configuration. 53 func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig { 54 s.mu.Lock() 55 defer s.mu.Unlock() 56 57 servConfig := registrytypes.ServiceConfig{ 58 InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0), 59 IndexConfigs: make(map[string]*(registrytypes.IndexInfo)), 60 Mirrors: make([]string, 0), 61 } 62 63 // construct a new ServiceConfig which will not retrieve s.Config directly, 64 // and look up items in s.config with mu locked 65 servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...) 66 67 for key, value := range s.config.ServiceConfig.IndexConfigs { 68 servConfig.IndexConfigs[key] = value 69 } 70 71 servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...) 72 73 return &servConfig 74 } 75 76 // LoadInsecureRegistries loads insecure registries for Service 77 func (s *DefaultService) LoadInsecureRegistries(registries []string) error { 78 s.mu.Lock() 79 defer s.mu.Unlock() 80 81 return s.config.LoadInsecureRegistries(registries) 82 } 83 84 // Auth contacts the public registry with the provided credentials, 85 // and returns OK if authentication was successful. 86 // It can be used to verify the validity of a client's credentials. 87 func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) { 88 // TODO Use ctx when searching for repositories 89 serverAddress := authConfig.ServerAddress 90 if serverAddress == "" { 91 serverAddress = IndexServer 92 } 93 if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") { 94 serverAddress = "https://" + serverAddress 95 } 96 u, err := url.Parse(serverAddress) 97 if err != nil { 98 return "", "", fmt.Errorf("unable to parse server address: %v", err) 99 } 100 101 endpoints, err := s.LookupPushEndpoints(u.Host) 102 if err != nil { 103 return "", "", err 104 } 105 106 for _, endpoint := range endpoints { 107 login := loginV2 108 if endpoint.Version == APIVersion1 { 109 login = loginV1 110 } 111 112 status, token, err = login(authConfig, endpoint, userAgent) 113 if err == nil { 114 return 115 } 116 if fErr, ok := err.(fallbackError); ok { 117 err = fErr.err 118 logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err) 119 continue 120 } 121 return "", "", err 122 } 123 124 return "", "", err 125 } 126 127 // splitReposSearchTerm breaks a search term into an index name and remote name 128 func splitReposSearchTerm(reposName string) (string, string) { 129 nameParts := strings.SplitN(reposName, "/", 2) 130 var indexName, remoteName string 131 if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && 132 !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { 133 // This is a Docker Index repos (ex: samalba/hipache or ubuntu) 134 // 'docker.io' 135 indexName = IndexName 136 remoteName = reposName 137 } else { 138 indexName = nameParts[0] 139 remoteName = nameParts[1] 140 } 141 return indexName, remoteName 142 } 143 144 // Search queries the public registry for images matching the specified 145 // search terms, and returns the results. 146 func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) { 147 // TODO Use ctx when searching for repositories 148 if err := validateNoScheme(term); err != nil { 149 return nil, err 150 } 151 152 indexName, remoteName := splitReposSearchTerm(term) 153 154 // Search is a long-running operation, just lock s.config to avoid block others. 155 s.mu.Lock() 156 index, err := newIndexInfo(s.config, indexName) 157 s.mu.Unlock() 158 159 if err != nil { 160 return nil, err 161 } 162 163 // *TODO: Search multiple indexes. 164 endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers)) 165 if err != nil { 166 return nil, err 167 } 168 169 var client *http.Client 170 if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" { 171 creds := NewStaticCredentialStore(authConfig) 172 scopes := []auth.Scope{ 173 auth.RegistryScope{ 174 Name: "catalog", 175 Actions: []string{"search"}, 176 }, 177 } 178 179 modifiers := DockerHeaders(userAgent, nil) 180 v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes) 181 if err != nil { 182 if fErr, ok := err.(fallbackError); ok { 183 logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err) 184 } else { 185 return nil, err 186 } 187 } else if foundV2 { 188 // Copy non transport http client features 189 v2Client.Timeout = endpoint.client.Timeout 190 v2Client.CheckRedirect = endpoint.client.CheckRedirect 191 v2Client.Jar = endpoint.client.Jar 192 193 logrus.Debugf("using v2 client for search to %s", endpoint.URL) 194 client = v2Client 195 } 196 } 197 198 if client == nil { 199 client = endpoint.client 200 if err := authorizeClient(client, authConfig, endpoint); err != nil { 201 return nil, err 202 } 203 } 204 205 r := newSession(client, authConfig, endpoint) 206 207 if index.Official { 208 localName := remoteName 209 if strings.HasPrefix(localName, "library/") { 210 // If pull "library/foo", it's stored locally under "foo" 211 localName = strings.SplitN(localName, "/", 2)[1] 212 } 213 214 return r.SearchRepositories(localName, limit) 215 } 216 return r.SearchRepositories(remoteName, limit) 217 } 218 219 // ResolveRepository splits a repository name into its components 220 // and configuration of the associated registry. 221 func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) { 222 s.mu.Lock() 223 defer s.mu.Unlock() 224 return newRepositoryInfo(s.config, name) 225 } 226 227 // APIEndpoint represents a remote API endpoint 228 type APIEndpoint struct { 229 Mirror bool 230 URL *url.URL 231 Version APIVersion 232 Official bool 233 TrimHostname bool 234 TLSConfig *tls.Config 235 } 236 237 // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint 238 func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) (*V1Endpoint, error) { 239 return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders) 240 } 241 242 // TLSConfig constructs a client TLS configuration based on server defaults 243 func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) { 244 s.mu.Lock() 245 defer s.mu.Unlock() 246 247 return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) 248 } 249 250 // tlsConfig constructs a client TLS configuration based on server defaults 251 func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) { 252 return newTLSConfig(hostname, isSecureIndex(s.config, hostname)) 253 } 254 255 func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) { 256 return s.tlsConfig(mirrorURL.Host) 257 } 258 259 // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference. 260 // It gives preference to v2 endpoints over v1, mirrors over the actual 261 // registry, and HTTPS over plain HTTP. 262 func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 263 s.mu.Lock() 264 defer s.mu.Unlock() 265 266 return s.lookupEndpoints(hostname) 267 } 268 269 // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference. 270 // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP. 271 // Mirrors are not included. 272 func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 273 s.mu.Lock() 274 defer s.mu.Unlock() 275 276 allEndpoints, err := s.lookupEndpoints(hostname) 277 if err == nil { 278 for _, endpoint := range allEndpoints { 279 if !endpoint.Mirror { 280 endpoints = append(endpoints, endpoint) 281 } 282 } 283 } 284 return endpoints, err 285 } 286 287 func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) { 288 endpoints, err = s.lookupV2Endpoints(hostname) 289 if err != nil { 290 return nil, err 291 } 292 293 if s.config.V2Only { 294 return endpoints, nil 295 } 296 297 legacyEndpoints, err := s.lookupV1Endpoints(hostname) 298 if err != nil { 299 return nil, err 300 } 301 endpoints = append(endpoints, legacyEndpoints...) 302 303 return endpoints, nil 304 }