registry.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. package plugin
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "net"
  6. "net/http"
  7. "time"
  8. "github.com/containerd/containerd/remotes"
  9. "github.com/containerd/containerd/remotes/docker"
  10. "github.com/containerd/log"
  11. "github.com/distribution/reference"
  12. "github.com/docker/docker/api/types/registry"
  13. "github.com/docker/docker/dockerversion"
  14. "github.com/pkg/errors"
  15. )
  16. // scope builds the correct auth scope for the registry client to authorize against
  17. // By default the client currently only does a "repository:" scope with out a classifier, e.g. "(plugin)"
  18. // Without this, the client will not be able to authorize the request
  19. func scope(ref reference.Named, push bool) string {
  20. scope := "repository(plugin):" + reference.Path(reference.TrimNamed(ref)) + ":pull"
  21. if push {
  22. scope += ",push"
  23. }
  24. return scope
  25. }
  26. func (pm *Manager) newResolver(ctx context.Context, tracker docker.StatusTracker, auth *registry.AuthConfig, headers http.Header, httpFallback bool) (remotes.Resolver, error) {
  27. if headers == nil {
  28. headers = http.Header{}
  29. }
  30. headers.Add("User-Agent", dockerversion.DockerUserAgent(ctx))
  31. return docker.NewResolver(docker.ResolverOptions{
  32. Tracker: tracker,
  33. Headers: headers,
  34. Hosts: pm.registryHostsFn(auth, httpFallback),
  35. }), nil
  36. }
  37. func registryHTTPClient(config *tls.Config) *http.Client {
  38. return &http.Client{
  39. Transport: &http.Transport{
  40. Proxy: http.ProxyFromEnvironment,
  41. DialContext: (&net.Dialer{
  42. Timeout: 30 * time.Second,
  43. KeepAlive: 30 * time.Second,
  44. }).DialContext,
  45. TLSClientConfig: config,
  46. TLSHandshakeTimeout: 10 * time.Second,
  47. IdleConnTimeout: 30 * time.Second,
  48. },
  49. }
  50. }
  51. func (pm *Manager) registryHostsFn(auth *registry.AuthConfig, httpFallback bool) docker.RegistryHosts {
  52. return func(hostname string) ([]docker.RegistryHost, error) {
  53. eps, err := pm.config.RegistryService.LookupPullEndpoints(hostname)
  54. if err != nil {
  55. return nil, errors.Wrapf(err, "error resolving repository for %s", hostname)
  56. }
  57. hosts := make([]docker.RegistryHost, 0, len(eps))
  58. for _, ep := range eps {
  59. // forced http fallback is used only for push since the containerd pusher only ever uses the first host we
  60. // pass to it.
  61. // So it is the callers responsibility to retry with this flag set.
  62. if httpFallback && ep.URL.Scheme != "http" {
  63. log.G(context.TODO()).WithField("registryHost", hostname).WithField("endpoint", ep).Debugf("Skipping non-http endpoint")
  64. continue
  65. }
  66. caps := docker.HostCapabilityPull | docker.HostCapabilityResolve
  67. if !ep.Mirror {
  68. caps = caps | docker.HostCapabilityPush
  69. }
  70. host, err := docker.DefaultHost(ep.URL.Host)
  71. if err != nil {
  72. return nil, err
  73. }
  74. client := registryHTTPClient(ep.TLSConfig)
  75. hosts = append(hosts, docker.RegistryHost{
  76. Host: host,
  77. Scheme: ep.URL.Scheme,
  78. Client: client,
  79. Path: "/v2",
  80. Capabilities: caps,
  81. Authorizer: docker.NewDockerAuthorizer(
  82. docker.WithAuthClient(client),
  83. docker.WithAuthCreds(func(_ string) (string, string, error) {
  84. if auth.IdentityToken != "" {
  85. return "", auth.IdentityToken, nil
  86. }
  87. return auth.Username, auth.Password, nil
  88. }),
  89. ),
  90. })
  91. }
  92. log.G(context.TODO()).WithField("registryHost", hostname).WithField("hosts", hosts).Debug("Resolved registry hosts")
  93. return hosts, nil
  94. }
  95. }