registry.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. package distribution
  2. import (
  3. "fmt"
  4. "net"
  5. "net/http"
  6. "time"
  7. "github.com/docker/distribution"
  8. "github.com/docker/distribution/manifest/schema2"
  9. "github.com/docker/distribution/reference"
  10. "github.com/docker/distribution/registry/client"
  11. "github.com/docker/distribution/registry/client/auth"
  12. "github.com/docker/distribution/registry/client/transport"
  13. "github.com/docker/docker/api/types"
  14. "github.com/docker/docker/dockerversion"
  15. "github.com/docker/docker/registry"
  16. "github.com/docker/go-connections/sockets"
  17. "golang.org/x/net/context"
  18. )
  19. // ImageTypes represents the schema2 config types for images
  20. var ImageTypes = []string{
  21. schema2.MediaTypeImageConfig,
  22. // Handle unexpected values from https://github.com/docker/distribution/issues/1621
  23. // (see also https://github.com/docker/docker/issues/22378,
  24. // https://github.com/docker/docker/issues/30083)
  25. "application/octet-stream",
  26. "application/json",
  27. "text/html",
  28. // Treat defaulted values as images, newer types cannot be implied
  29. "",
  30. }
  31. // PluginTypes represents the schema2 config types for plugins
  32. var PluginTypes = []string{
  33. schema2.MediaTypePluginConfig,
  34. }
  35. var mediaTypeClasses map[string]string
  36. func init() {
  37. // initialize media type classes with all know types for
  38. // plugin
  39. mediaTypeClasses = map[string]string{}
  40. for _, t := range ImageTypes {
  41. mediaTypeClasses[t] = "image"
  42. }
  43. for _, t := range PluginTypes {
  44. mediaTypeClasses[t] = "plugin"
  45. }
  46. }
  47. // NewV2Repository returns a repository (v2 only). It creates an HTTP transport
  48. // providing timeout settings and authentication support, and also verifies the
  49. // remote API version.
  50. func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, endpoint registry.APIEndpoint, metaHeaders http.Header, authConfig *types.AuthConfig, actions ...string) (repo distribution.Repository, foundVersion bool, err error) {
  51. repoName := repoInfo.Name.Name()
  52. // If endpoint does not support CanonicalName, use the RemoteName instead
  53. if endpoint.TrimHostname {
  54. repoName = reference.Path(repoInfo.Name)
  55. }
  56. direct := &net.Dialer{
  57. Timeout: 30 * time.Second,
  58. KeepAlive: 30 * time.Second,
  59. DualStack: true,
  60. }
  61. // TODO(dmcgowan): Call close idle connections when complete, use keep alive
  62. base := &http.Transport{
  63. Proxy: http.ProxyFromEnvironment,
  64. Dial: direct.Dial,
  65. TLSHandshakeTimeout: 10 * time.Second,
  66. TLSClientConfig: endpoint.TLSConfig,
  67. // TODO(dmcgowan): Call close idle connections when complete and use keep alive
  68. DisableKeepAlives: true,
  69. }
  70. proxyDialer, err := sockets.DialerFromEnvironment(direct)
  71. if err == nil {
  72. base.Dial = proxyDialer.Dial
  73. }
  74. modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders)
  75. authTransport := transport.NewTransport(base, modifiers...)
  76. challengeManager, foundVersion, err := registry.PingV2Registry(endpoint.URL, authTransport)
  77. if err != nil {
  78. transportOK := false
  79. if responseErr, ok := err.(registry.PingResponseError); ok {
  80. transportOK = true
  81. err = responseErr.Err
  82. }
  83. return nil, foundVersion, fallbackError{
  84. err: err,
  85. confirmedV2: foundVersion,
  86. transportOK: transportOK,
  87. }
  88. }
  89. if authConfig.RegistryToken != "" {
  90. passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
  91. modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
  92. } else {
  93. scope := auth.RepositoryScope{
  94. Repository: repoName,
  95. Actions: actions,
  96. Class: repoInfo.Class,
  97. }
  98. creds := registry.NewStaticCredentialStore(authConfig)
  99. tokenHandlerOptions := auth.TokenHandlerOptions{
  100. Transport: authTransport,
  101. Credentials: creds,
  102. Scopes: []auth.Scope{scope},
  103. ClientID: registry.AuthClientID,
  104. }
  105. tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
  106. basicHandler := auth.NewBasicHandler(creds)
  107. modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
  108. }
  109. tr := transport.NewTransport(base, modifiers...)
  110. repoNameRef, err := reference.WithName(repoName)
  111. if err != nil {
  112. return nil, foundVersion, fallbackError{
  113. err: err,
  114. confirmedV2: foundVersion,
  115. transportOK: true,
  116. }
  117. }
  118. repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL.String(), tr)
  119. if err != nil {
  120. err = fallbackError{
  121. err: err,
  122. confirmedV2: foundVersion,
  123. transportOK: true,
  124. }
  125. }
  126. return
  127. }
  128. type existingTokenHandler struct {
  129. token string
  130. }
  131. func (th *existingTokenHandler) Scheme() string {
  132. return "bearer"
  133. }
  134. func (th *existingTokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
  135. req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", th.token))
  136. return nil
  137. }