pull.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. package distribution // import "github.com/docker/docker/distribution"
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/containerd/log"
  6. "github.com/distribution/reference"
  7. "github.com/docker/docker/api"
  8. "github.com/docker/docker/api/types/events"
  9. refstore "github.com/docker/docker/reference"
  10. "github.com/docker/docker/registry"
  11. "github.com/opencontainers/go-digest"
  12. "github.com/pkg/errors"
  13. )
  14. // Pull initiates a pull operation. image is the repository name to pull, and
  15. // tag may be either empty, or indicate a specific tag to pull.
  16. func Pull(ctx context.Context, ref reference.Named, config *ImagePullConfig, local ContentStore) error {
  17. repoInfo, err := pullEndpoints(ctx, config.RegistryService, ref, func(ctx context.Context, repoInfo registry.RepositoryInfo, endpoint registry.APIEndpoint) error {
  18. log.G(ctx).Debugf("Trying to pull %s from %s", reference.FamiliarName(repoInfo.Name), endpoint.URL)
  19. puller := newPuller(endpoint, &repoInfo, config, local)
  20. return puller.pull(ctx, ref)
  21. })
  22. if err == nil {
  23. config.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), events.ActionPull)
  24. }
  25. return err
  26. }
  27. // Tags returns available tags for the given image in the remote repository.
  28. func Tags(ctx context.Context, ref reference.Named, config *Config) ([]string, error) {
  29. var tags []string
  30. _, err := pullEndpoints(ctx, config.RegistryService, ref, func(ctx context.Context, repoInfo registry.RepositoryInfo, endpoint registry.APIEndpoint) error {
  31. repo, err := newRepository(ctx, &repoInfo, endpoint, config.MetaHeaders, config.AuthConfig, "pull")
  32. if err != nil {
  33. return err
  34. }
  35. tags, err = repo.Tags(ctx).All(ctx)
  36. return err
  37. })
  38. return tags, err
  39. }
  40. // validateRepoName validates the name of a repository.
  41. func validateRepoName(name reference.Named) error {
  42. if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
  43. return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier))
  44. }
  45. return nil
  46. }
  47. func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error {
  48. dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst)
  49. if err != nil {
  50. return err
  51. }
  52. if oldTagID, err := store.Get(dgstRef); err == nil {
  53. if oldTagID != id {
  54. // Updating digests not supported by reference store
  55. log.G(context.TODO()).Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id)
  56. }
  57. return nil
  58. } else if err != refstore.ErrDoesNotExist {
  59. return err
  60. }
  61. return store.AddDigest(dgstRef, id, true)
  62. }
  63. func pullEndpoints(ctx context.Context, registryService RegistryResolver, ref reference.Named,
  64. f func(context.Context, registry.RepositoryInfo, registry.APIEndpoint) error,
  65. ) (*registry.RepositoryInfo, error) {
  66. // Resolve the Repository name from fqn to RepositoryInfo
  67. repoInfo, err := registryService.ResolveRepository(ref)
  68. if err != nil {
  69. return nil, err
  70. }
  71. // makes sure name is not `scratch`
  72. if err := validateRepoName(repoInfo.Name); err != nil {
  73. return repoInfo, err
  74. }
  75. endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
  76. if err != nil {
  77. return repoInfo, err
  78. }
  79. var (
  80. lastErr error
  81. // confirmedTLSRegistries is a map indicating which registries
  82. // are known to be using TLS. There should never be a plaintext
  83. // retry for any of these.
  84. confirmedTLSRegistries = make(map[string]struct{})
  85. )
  86. for _, endpoint := range endpoints {
  87. if endpoint.URL.Scheme != "https" {
  88. if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
  89. log.G(ctx).Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
  90. continue
  91. }
  92. }
  93. log.G(ctx).Debugf("Trying to pull %s from %s", reference.FamiliarName(repoInfo.Name), endpoint.URL)
  94. if err := f(ctx, *repoInfo, endpoint); err != nil {
  95. if _, ok := err.(fallbackError); !ok && continueOnError(err, endpoint.Mirror) {
  96. err = fallbackError{
  97. err: err,
  98. transportOK: true,
  99. }
  100. }
  101. // Was this pull cancelled? If so, don't try to fall
  102. // back.
  103. fallback := false
  104. select {
  105. case <-ctx.Done():
  106. default:
  107. if fallbackErr, ok := err.(fallbackError); ok {
  108. fallback = true
  109. if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
  110. confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
  111. }
  112. err = fallbackErr.err
  113. }
  114. }
  115. if fallback {
  116. lastErr = err
  117. log.G(ctx).Infof("Attempting next endpoint for pull after error: %v", err)
  118. continue
  119. }
  120. log.G(ctx).Errorf("Not continuing with pull after error: %v", err)
  121. return repoInfo, translatePullError(err, ref)
  122. }
  123. return repoInfo, nil
  124. }
  125. if lastErr == nil {
  126. lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref))
  127. }
  128. return repoInfo, translatePullError(lastErr, ref)
  129. }