github.com/afbjorklund/moby@v20.10.5+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 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/registry" 19 digest "github.com/opencontainers/go-digest" 20 specs "github.com/opencontainers/image-spec/specs-go/v1" 21 "github.com/pkg/errors" 22 ) 23 24 // PullImage initiates a pull operation. image is the repository name to pull, and 25 // tag may be either empty, or indicate a specific tag to pull. 26 func (i *ImageService) PullImage(ctx context.Context, image, tag string, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { 27 start := time.Now() 28 // Special case: "pull -a" may send an image name with a 29 // trailing :. This is ugly, but let's not break API 30 // compatibility. 31 image = strings.TrimSuffix(image, ":") 32 33 ref, err := reference.ParseNormalizedNamed(image) 34 if err != nil { 35 return errdefs.InvalidParameter(err) 36 } 37 38 if tag != "" { 39 // The "tag" could actually be a digest. 40 var dgst digest.Digest 41 dgst, err = digest.Parse(tag) 42 if err == nil { 43 ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst) 44 } else { 45 ref, err = reference.WithTag(ref, tag) 46 } 47 if err != nil { 48 return errdefs.InvalidParameter(err) 49 } 50 } 51 52 err = i.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream) 53 imageActions.WithValues("pull").UpdateSince(start) 54 return err 55 } 56 57 func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *specs.Platform, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { 58 // Include a buffer so that slow client connections don't affect 59 // transfer performance. 60 progressChan := make(chan progress.Progress, 100) 61 62 writesDone := make(chan struct{}) 63 64 ctx, cancelFunc := context.WithCancel(ctx) 65 66 go func() { 67 progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan) 68 close(writesDone) 69 }() 70 71 ctx = namespaces.WithNamespace(ctx, i.contentNamespace) 72 // Take out a temporary lease for everything that gets persisted to the content store. 73 // Before the lease is cancelled, any content we want to keep should have it's own lease applied. 74 ctx, done, err := tempLease(ctx, i.leases) 75 if err != nil { 76 return err 77 } 78 defer done(ctx) 79 80 cs := &contentStoreForPull{ 81 ContentStore: i.content, 82 leases: i.leases, 83 } 84 imageStore := &imageStoreForPull{ 85 ImageConfigStore: distribution.NewImageConfigStoreFromStore(i.imageStore), 86 ingested: cs, 87 leases: i.leases, 88 } 89 90 imagePullConfig := &distribution.ImagePullConfig{ 91 Config: distribution.Config{ 92 MetaHeaders: metaHeaders, 93 AuthConfig: authConfig, 94 ProgressOutput: progress.ChanOutput(progressChan), 95 RegistryService: i.registryService, 96 ImageEventLogger: i.LogImageEvent, 97 MetadataStore: i.distributionMetadataStore, 98 ImageStore: imageStore, 99 ReferenceStore: i.referenceStore, 100 }, 101 DownloadManager: i.downloadManager, 102 Schema2Types: distribution.ImageTypes, 103 Platform: platform, 104 } 105 106 err = distribution.Pull(ctx, ref, imagePullConfig, cs) 107 close(progressChan) 108 <-writesDone 109 return err 110 } 111 112 // GetRepository returns a repository from the registry. 113 func (i *ImageService) GetRepository(ctx context.Context, ref reference.Named, authConfig *types.AuthConfig) (dist.Repository, bool, error) { 114 // get repository info 115 repoInfo, err := i.registryService.ResolveRepository(ref) 116 if err != nil { 117 return nil, false, errdefs.InvalidParameter(err) 118 } 119 // makes sure name is not empty or `scratch` 120 if err := distribution.ValidateRepoName(repoInfo.Name); err != nil { 121 return nil, false, errdefs.InvalidParameter(err) 122 } 123 124 // get endpoints 125 endpoints, err := i.registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) 126 if err != nil { 127 return nil, false, err 128 } 129 130 // retrieve repository 131 var ( 132 confirmedV2 bool 133 repository dist.Repository 134 lastError error 135 ) 136 137 for _, endpoint := range endpoints { 138 if endpoint.Version == registry.APIVersion1 { 139 continue 140 } 141 142 repository, confirmedV2, lastError = distribution.NewV2Repository(ctx, repoInfo, endpoint, nil, authConfig, "pull") 143 if lastError == nil && confirmedV2 { 144 break 145 } 146 } 147 return repository, confirmedV2, lastError 148 } 149 150 func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) { 151 nop := func(context.Context) error { return nil } 152 _, ok := leases.FromContext(ctx) 153 if ok { 154 return ctx, nop, nil 155 } 156 157 // Use an expiration that ensures the lease is cleaned up at some point if there is a crash, SIGKILL, etc. 158 opts := []leases.Opt{ 159 leases.WithRandomID(), 160 leases.WithExpiration(24 * time.Hour), 161 leases.WithLabels(map[string]string{ 162 "moby.lease/temporary": time.Now().UTC().Format(time.RFC3339Nano), 163 }), 164 } 165 l, err := mgr.Create(ctx, opts...) 166 if err != nil { 167 return ctx, nop, errors.Wrap(err, "error creating temporary lease") 168 } 169 170 ctx = leases.WithLease(ctx, l.ID) 171 return ctx, func(ctx context.Context) error { 172 return mgr.Delete(ctx, l) 173 }, nil 174 }