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