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