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