github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/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 "github.com/docker/docker/api/types" 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 digest "github.com/opencontainers/go-digest" 20 specs "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 *specs.Platform, metaHeaders map[string][]string, authConfig *types.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 ass logs it to the daemon logs. 66 img, err := i.GetImage(image, 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 } 75 } 76 77 return nil 78 } 79 80 func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { 81 // Include a buffer so that slow client connections don't affect 82 // transfer performance. 83 progressChan := make(chan progress.Progress, 100) 84 85 writesDone := make(chan struct{}) 86 87 ctx, cancelFunc := context.WithCancel(ctx) 88 89 go func() { 90 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 91 close(writesDone) 92 }() 93 94 ctx = namespaces.WithNamespace(ctx, i.contentNamespace) 95 // Take out a temporary lease for everything that gets persisted to the content store. 96 // Before the lease is cancelled, any content we want to keep should have it's own lease applied. 97 ctx, done, err := tempLease(ctx, i.leases) 98 if err != nil { 99 return err 100 } 101 defer done(ctx) 102 103 cs := &contentStoreForPull{ 104 ContentStore: i.content, 105 leases: i.leases, 106 } 107 imageStore := &imageStoreForPull{ 108 ImageConfigStore: distribution.NewImageConfigStoreFromStore(i.imageStore), 109 ingested: cs, 110 leases: i.leases, 111 } 112 113 imagePullConfig := &distribution.ImagePullConfig{ 114 Config: distribution.Config{ 115 MetaHeaders: metaHeaders, 116 AuthConfig: authConfig, 117 ProgressOutput: progress.ChanOutput(progressChan), 118 RegistryService: i.registryService, 119 ImageEventLogger: i.LogImageEvent, 120 MetadataStore: i.distributionMetadataStore, 121 ImageStore: imageStore, 122 ReferenceStore: i.referenceStore, 123 }, 124 DownloadManager: i.downloadManager, 125 Schema2Types: distribution.ImageTypes, 126 Platform: platform, 127 } 128 129 err = distribution.Pull(ctx, ref, imagePullConfig, cs) 130 close(progressChan) 131 <-writesDone 132 return err 133 } 134 135 // GetRepository returns a repository from the registry. 136 func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, authConfig *types.AuthConfig) (dist.Repository, error) { 137 // get repository info 138 repoInfo, err := i.registryService.ResolveRepository(ref) 139 if err != nil { 140 return nil, errdefs.InvalidParameter(err) 141 } 142 // makes sure name is not empty or `scratch` 143 if err := distribution.ValidateRepoName(repoInfo.Name); err != nil { 144 return nil, errdefs.InvalidParameter(err) 145 } 146 147 // get endpoints 148 endpoints, err := i.registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) 149 if err != nil { 150 return nil, err 151 } 152 153 // retrieve repository 154 var ( 155 repository dist.Repository 156 lastError error 157 ) 158 159 for _, endpoint := range endpoints { 160 repository, lastError = distribution.NewV2Repository(ctx, repoInfo, endpoint, nil, authConfig, "pull") 161 if lastError == nil { 162 break 163 } 164 } 165 return repository, lastError 166 } 167 168 func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) { 169 nop := func(context.Context) error { return nil } 170 _, ok := leases.FromContext(ctx) 171 if ok { 172 return ctx, nop, nil 173 } 174 175 // Use an expiration that ensures the lease is cleaned up at some point if there is a crash, SIGKILL, etc. 176 opts := []leases.Opt{ 177 leases.WithRandomID(), 178 leases.WithExpiration(24 * time.Hour), 179 leases.WithLabels(map[string]string{ 180 "moby.lease/temporary": time.Now().UTC().Format(time.RFC3339Nano), 181 }), 182 } 183 l, err := mgr.Create(ctx, opts...) 184 if err != nil { 185 return ctx, nop, errors.Wrap(err, "error creating temporary lease") 186 } 187 188 ctx = leases.WithLease(ctx, l.ID) 189 return ctx, func(ctx context.Context) error { 190 return mgr.Delete(ctx, l) 191 }, nil 192 }