image_pull.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package images // import "github.com/docker/docker/daemon/images"
  2. import (
  3. "context"
  4. "io"
  5. "strings"
  6. "time"
  7. "github.com/containerd/containerd/leases"
  8. "github.com/containerd/containerd/namespaces"
  9. "github.com/docker/distribution/reference"
  10. imagetypes "github.com/docker/docker/api/types/image"
  11. "github.com/docker/docker/api/types/registry"
  12. "github.com/docker/docker/distribution"
  13. progressutils "github.com/docker/docker/distribution/utils"
  14. "github.com/docker/docker/errdefs"
  15. "github.com/docker/docker/pkg/progress"
  16. "github.com/docker/docker/pkg/streamformatter"
  17. "github.com/opencontainers/go-digest"
  18. specs "github.com/opencontainers/image-spec/specs-go/v1"
  19. "github.com/pkg/errors"
  20. "github.com/sirupsen/logrus"
  21. )
  22. // PullImage initiates a pull operation. image is the repository name to pull, and
  23. // tag may be either empty, or indicate a specific tag to pull.
  24. func (i *ImageService) PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
  25. start := time.Now()
  26. // Special case: "pull -a" may send an image name with a
  27. // trailing :. This is ugly, but let's not break API
  28. // compatibility.
  29. image = strings.TrimSuffix(image, ":")
  30. ref, err := reference.ParseNormalizedNamed(image)
  31. if err != nil {
  32. return errdefs.InvalidParameter(err)
  33. }
  34. if tag != "" {
  35. // The "tag" could actually be a digest.
  36. var dgst digest.Digest
  37. dgst, err = digest.Parse(tag)
  38. if err == nil {
  39. ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst)
  40. } else {
  41. ref, err = reference.WithTag(ref, tag)
  42. }
  43. if err != nil {
  44. return errdefs.InvalidParameter(err)
  45. }
  46. }
  47. err = i.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream)
  48. imageActions.WithValues("pull").UpdateSince(start)
  49. if err != nil {
  50. return err
  51. }
  52. if platform != nil {
  53. // If --platform was specified, check that the image we pulled matches
  54. // the expected platform. This check is for situations where the image
  55. // is a single-arch image, in which case (for backward compatibility),
  56. // we allow the image to have a non-matching architecture. The code
  57. // below checks for this situation, and returns a warning to the client,
  58. // as well as logging it to the daemon logs.
  59. img, err := i.GetImage(ctx, image, imagetypes.GetImageOpts{Platform: platform})
  60. // Note that this is a special case where GetImage returns both an image
  61. // and an error: https://github.com/docker/docker/blob/v20.10.7/daemon/images/image.go#L175-L183
  62. if errdefs.IsNotFound(err) && img != nil {
  63. po := streamformatter.NewJSONProgressOutput(outStream, false)
  64. progress.Messagef(po, "", `WARNING: %s`, err.Error())
  65. logrus.WithError(err).WithField("image", image).Warn("ignoring platform mismatch on single-arch image")
  66. } else if err != nil {
  67. return err
  68. }
  69. }
  70. return nil
  71. }
  72. func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *specs.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
  73. // Include a buffer so that slow client connections don't affect
  74. // transfer performance.
  75. progressChan := make(chan progress.Progress, 100)
  76. writesDone := make(chan struct{})
  77. ctx, cancelFunc := context.WithCancel(ctx)
  78. go func() {
  79. progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
  80. close(writesDone)
  81. }()
  82. ctx = namespaces.WithNamespace(ctx, i.contentNamespace)
  83. // Take out a temporary lease for everything that gets persisted to the content store.
  84. // Before the lease is cancelled, any content we want to keep should have it's own lease applied.
  85. ctx, done, err := tempLease(ctx, i.leases)
  86. if err != nil {
  87. return err
  88. }
  89. defer done(ctx)
  90. cs := &contentStoreForPull{
  91. ContentStore: i.content,
  92. leases: i.leases,
  93. }
  94. imageStore := &imageStoreForPull{
  95. ImageConfigStore: distribution.NewImageConfigStoreFromStore(i.imageStore),
  96. ingested: cs,
  97. leases: i.leases,
  98. }
  99. imagePullConfig := &distribution.ImagePullConfig{
  100. Config: distribution.Config{
  101. MetaHeaders: metaHeaders,
  102. AuthConfig: authConfig,
  103. ProgressOutput: progress.ChanOutput(progressChan),
  104. RegistryService: i.registryService,
  105. ImageEventLogger: i.LogImageEvent,
  106. MetadataStore: i.distributionMetadataStore,
  107. ImageStore: imageStore,
  108. ReferenceStore: i.referenceStore,
  109. },
  110. DownloadManager: i.downloadManager,
  111. Platform: platform,
  112. }
  113. err = distribution.Pull(ctx, ref, imagePullConfig, cs)
  114. close(progressChan)
  115. <-writesDone
  116. return err
  117. }
  118. func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) {
  119. nop := func(context.Context) error { return nil }
  120. _, ok := leases.FromContext(ctx)
  121. if ok {
  122. return ctx, nop, nil
  123. }
  124. // Use an expiration that ensures the lease is cleaned up at some point if there is a crash, SIGKILL, etc.
  125. opts := []leases.Opt{
  126. leases.WithRandomID(),
  127. leases.WithExpiration(24 * time.Hour),
  128. leases.WithLabels(map[string]string{
  129. "moby.lease/temporary": time.Now().UTC().Format(time.RFC3339Nano),
  130. }),
  131. }
  132. l, err := mgr.Create(ctx, opts...)
  133. if err != nil {
  134. return ctx, nop, errors.Wrap(err, "error creating temporary lease")
  135. }
  136. ctx = leases.WithLease(ctx, l.ID)
  137. return ctx, func(ctx context.Context) error {
  138. return mgr.Delete(ctx, l)
  139. }, nil
  140. }