service.go 8.1 KB

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