pull.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package distribution
  2. import (
  3. "fmt"
  4. "runtime"
  5. "github.com/docker/distribution/reference"
  6. "github.com/docker/docker/api"
  7. "github.com/docker/docker/distribution/metadata"
  8. "github.com/docker/docker/pkg/progress"
  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. "github.com/sirupsen/logrus"
  14. "golang.org/x/net/context"
  15. )
  16. // Puller is an interface that abstracts pulling for different API versions.
  17. type Puller interface {
  18. // Pull tries to pull the image referenced by `tag`
  19. // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint.
  20. //
  21. Pull(ctx context.Context, ref reference.Named, platform string) error
  22. }
  23. // newPuller returns a Puller interface that will pull from either a v1 or v2
  24. // registry. The endpoint argument contains a Version field that determines
  25. // whether a v1 or v2 puller will be created. The other parameters are passed
  26. // through to the underlying puller implementation for use during the actual
  27. // pull operation.
  28. func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) {
  29. switch endpoint.Version {
  30. case registry.APIVersion2:
  31. return &v2Puller{
  32. V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore),
  33. endpoint: endpoint,
  34. config: imagePullConfig,
  35. repoInfo: repoInfo,
  36. }, nil
  37. case registry.APIVersion1:
  38. return &v1Puller{
  39. v1IDService: metadata.NewV1IDService(imagePullConfig.MetadataStore),
  40. endpoint: endpoint,
  41. config: imagePullConfig,
  42. repoInfo: repoInfo,
  43. }, nil
  44. }
  45. return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
  46. }
  47. // Pull initiates a pull operation. image is the repository name to pull, and
  48. // tag may be either empty, or indicate a specific tag to pull.
  49. func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig) error {
  50. // Resolve the Repository name from fqn to RepositoryInfo
  51. repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref)
  52. if err != nil {
  53. return err
  54. }
  55. // makes sure name is not `scratch`
  56. if err := ValidateRepoName(repoInfo.Name); err != nil {
  57. return err
  58. }
  59. endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
  60. if err != nil {
  61. return err
  62. }
  63. var (
  64. lastErr error
  65. // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport
  66. // By default it is false, which means that if an ErrNoSupport error is encountered, it will be saved in lastErr.
  67. // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of
  68. // any subsequent ErrNoSupport errors in lastErr.
  69. // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be
  70. // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant
  71. // error is the ones from v2 endpoints not v1.
  72. discardNoSupportErrors bool
  73. // confirmedV2 is set to true if a pull attempt managed to
  74. // confirm that it was talking to a v2 registry. This will
  75. // prevent fallback to the v1 protocol.
  76. confirmedV2 bool
  77. // confirmedTLSRegistries is a map indicating which registries
  78. // are known to be using TLS. There should never be a plaintext
  79. // retry for any of these.
  80. confirmedTLSRegistries = make(map[string]struct{})
  81. )
  82. for _, endpoint := range endpoints {
  83. if imagePullConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 {
  84. continue
  85. }
  86. if confirmedV2 && endpoint.Version == registry.APIVersion1 {
  87. logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
  88. continue
  89. }
  90. if endpoint.URL.Scheme != "https" {
  91. if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
  92. logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
  93. continue
  94. }
  95. }
  96. logrus.Debugf("Trying to pull %s from %s %s", reference.FamiliarName(repoInfo.Name), endpoint.URL, endpoint.Version)
  97. puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
  98. if err != nil {
  99. lastErr = err
  100. continue
  101. }
  102. // Make sure we default the platform if it hasn't been supplied
  103. if imagePullConfig.Platform == "" {
  104. imagePullConfig.Platform = runtime.GOOS
  105. }
  106. if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil {
  107. // Was this pull cancelled? If so, don't try to fall
  108. // back.
  109. fallback := false
  110. select {
  111. case <-ctx.Done():
  112. default:
  113. if fallbackErr, ok := err.(fallbackError); ok {
  114. fallback = true
  115. confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
  116. if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
  117. confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
  118. }
  119. err = fallbackErr.err
  120. }
  121. }
  122. if fallback {
  123. if _, ok := err.(ErrNoSupport); !ok {
  124. // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors.
  125. discardNoSupportErrors = true
  126. // append subsequent errors
  127. lastErr = err
  128. } else if !discardNoSupportErrors {
  129. // Save the ErrNoSupport error, because it's either the first error or all encountered errors
  130. // were also ErrNoSupport errors.
  131. // append subsequent errors
  132. lastErr = err
  133. }
  134. logrus.Infof("Attempting next endpoint for pull after error: %v", err)
  135. continue
  136. }
  137. logrus.Errorf("Not continuing with pull after error: %v", err)
  138. return TranslatePullError(err, ref)
  139. }
  140. imagePullConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "pull")
  141. return nil
  142. }
  143. if lastErr == nil {
  144. lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref))
  145. }
  146. return TranslatePullError(lastErr, ref)
  147. }
  148. // writeStatus writes a status message to out. If layersDownloaded is true, the
  149. // status message indicates that a newer image was downloaded. Otherwise, it
  150. // indicates that the image is up to date. requestedTag is the tag the message
  151. // will refer to.
  152. func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) {
  153. if layersDownloaded {
  154. progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag)
  155. } else {
  156. progress.Message(out, "", "Status: Image is up to date for "+requestedTag)
  157. }
  158. }
  159. // ValidateRepoName validates the name of a repository.
  160. func ValidateRepoName(name reference.Named) error {
  161. if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
  162. return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier))
  163. }
  164. return nil
  165. }
  166. func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error {
  167. dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst)
  168. if err != nil {
  169. return err
  170. }
  171. if oldTagID, err := store.Get(dgstRef); err == nil {
  172. if oldTagID != id {
  173. // Updating digests not supported by reference store
  174. logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id)
  175. }
  176. return nil
  177. } else if err != refstore.ErrDoesNotExist {
  178. return err
  179. }
  180. return store.AddDigest(dgstRef, id, true)
  181. }