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