github.com/rish1988/moby@v25.0.2+incompatible/daemon/containerd/image_pull.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/containerd/containerd" 10 cerrdefs "github.com/containerd/containerd/errdefs" 11 "github.com/containerd/containerd/images" 12 "github.com/containerd/containerd/pkg/snapshotters" 13 "github.com/containerd/containerd/platforms" 14 "github.com/containerd/containerd/remotes/docker" 15 "github.com/containerd/log" 16 "github.com/distribution/reference" 17 "github.com/docker/docker/api/types/events" 18 registrytypes "github.com/docker/docker/api/types/registry" 19 "github.com/docker/docker/distribution" 20 "github.com/docker/docker/errdefs" 21 "github.com/docker/docker/internal/compatcontext" 22 "github.com/docker/docker/pkg/progress" 23 "github.com/docker/docker/pkg/streamformatter" 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "github.com/pkg/errors" 26 ) 27 28 // PullImage initiates a pull operation. baseRef is the image to pull. 29 // If reference is not tagged, all tags are pulled. 30 func (i *ImageService) PullImage(ctx context.Context, baseRef reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registrytypes.AuthConfig, outStream io.Writer) error { 31 out := streamformatter.NewJSONProgressOutput(outStream, false) 32 33 if !reference.IsNameOnly(baseRef) { 34 return i.pullTag(ctx, baseRef, platform, metaHeaders, authConfig, out) 35 } 36 37 tags, err := distribution.Tags(ctx, baseRef, &distribution.Config{ 38 RegistryService: i.registryService, 39 MetaHeaders: metaHeaders, 40 AuthConfig: authConfig, 41 }) 42 if err != nil { 43 return err 44 } 45 46 for _, tag := range tags { 47 ref, err := reference.WithTag(baseRef, tag) 48 if err != nil { 49 log.G(ctx).WithFields(log.Fields{ 50 "tag": tag, 51 "baseRef": baseRef, 52 }).Warn("invalid tag, won't pull") 53 continue 54 } 55 56 if err := i.pullTag(ctx, ref, platform, metaHeaders, authConfig, out); err != nil { 57 return fmt.Errorf("error pulling %s: %w", ref, err) 58 } 59 } 60 61 return nil 62 } 63 64 func (i *ImageService) pullTag(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registrytypes.AuthConfig, out progress.Output) error { 65 var opts []containerd.RemoteOpt 66 if platform != nil { 67 opts = append(opts, containerd.WithPlatform(platforms.Format(*platform))) 68 } 69 70 resolver, _ := i.newResolverFromAuthConfig(ctx, authConfig, ref) 71 opts = append(opts, containerd.WithResolver(resolver)) 72 73 old, err := i.resolveDescriptor(ctx, ref.String()) 74 if err != nil && !errdefs.IsNotFound(err) { 75 return err 76 } 77 p := platforms.Default() 78 if platform != nil { 79 p = platforms.Only(*platform) 80 } 81 82 jobs := newJobs() 83 h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 84 if images.IsLayerType(desc.MediaType) { 85 jobs.Add(desc) 86 } 87 return nil, nil 88 }) 89 opts = append(opts, containerd.WithImageHandler(h)) 90 91 pp := pullProgress{store: i.client.ContentStore(), showExists: true} 92 finishProgress := jobs.showProgress(ctx, out, pp) 93 94 var outNewImg *containerd.Image 95 defer func() { 96 finishProgress() 97 98 // Send final status message after the progress updater has finished. 99 // Otherwise the layer/manifest progress messages may arrive AFTER the 100 // status message have been sent, so they won't update the previous 101 // progress leaving stale progress like: 102 // 70f5ac315c5a: Downloading [> ] 0B/3.19kB 103 // Digest: sha256:4f53e2564790c8e7856ec08e384732aa38dc43c52f02952483e3f003afbf23db 104 // 70f5ac315c5a: Download complete 105 // Status: Downloaded newer image for hello-world:latest 106 // docker.io/library/hello-world:latest 107 if outNewImg != nil { 108 img := *outNewImg 109 progress.Message(out, "", "Digest: "+img.Target().Digest.String()) 110 writeStatus(out, reference.FamiliarString(ref), old.Digest != img.Target().Digest) 111 } 112 }() 113 114 var sentPullingFrom, sentSchema1Deprecation bool 115 ah := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { 116 if desc.MediaType == images.MediaTypeDockerSchema1Manifest && !sentSchema1Deprecation { 117 progress.Message(out, "", distribution.DeprecatedSchema1ImageMessage(ref)) 118 sentSchema1Deprecation = true 119 } 120 if images.IsManifestType(desc.MediaType) { 121 if !sentPullingFrom { 122 var tagOrDigest string 123 if tagged, ok := ref.(reference.Tagged); ok { 124 tagOrDigest = tagged.Tag() 125 } else { 126 tagOrDigest = ref.String() 127 } 128 progress.Message(out, tagOrDigest, "Pulling from "+reference.Path(ref)) 129 sentPullingFrom = true 130 } 131 132 available, _, _, missing, err := images.Check(ctx, i.client.ContentStore(), desc, p) 133 if err != nil { 134 return nil, err 135 } 136 // If we already have all the contents pull shouldn't show any layer 137 // download progress, not even a "Already present" message. 138 if available && len(missing) == 0 { 139 pp.hideLayers = true 140 } 141 } 142 return nil, nil 143 }) 144 opts = append(opts, containerd.WithImageHandler(ah)) 145 146 opts = append(opts, containerd.WithPullUnpack) 147 // TODO(thaJeztah): we may have to pass the snapshotter to use if the pull is part of a "docker run" (container create -> pull image if missing). See https://github.com/moby/moby/issues/45273 148 opts = append(opts, containerd.WithPullSnapshotter(i.snapshotter)) 149 150 // AppendInfoHandlerWrapper will annotate the image with basic information like manifest and layer digests as labels; 151 // this information is used to enable remote snapshotters like nydus and stargz to query a registry. 152 infoHandler := snapshotters.AppendInfoHandlerWrapper(ref.String()) 153 opts = append(opts, containerd.WithImageHandlerWrapper(infoHandler)) 154 155 // Allow pulling application/vnd.docker.distribution.manifest.v1+prettyjws images 156 // by converting them to OCI manifests. 157 opts = append(opts, containerd.WithSchema1Conversion) //nolint:staticcheck // Ignore SA1019: containerd.WithSchema1Conversion is deprecated: use Schema 2 or OCI images. 158 159 img, err := i.client.Pull(ctx, ref.String(), opts...) 160 if err != nil { 161 if errors.Is(err, docker.ErrInvalidAuthorization) { 162 // Match error returned by containerd. 163 // https://github.com/containerd/containerd/blob/v1.7.8/remotes/docker/authorizer.go#L189-L191 164 if strings.Contains(err.Error(), "no basic auth credentials") { 165 return err 166 } 167 return errdefs.NotFound(fmt.Errorf("pull access denied for %s, repository does not exist or may require 'docker login'", reference.FamiliarName(ref))) 168 } 169 return err 170 } 171 172 logger := log.G(ctx).WithFields(log.Fields{ 173 "digest": img.Target().Digest, 174 "remote": ref.String(), 175 }) 176 logger.Info("image pulled") 177 178 // The pull succeeded, so try to remove any dangling image we have for this target 179 err = i.client.ImageService().Delete(compatcontext.WithoutCancel(ctx), danglingImageName(img.Target().Digest)) 180 if err != nil && !cerrdefs.IsNotFound(err) { 181 // Image pull succeeded, but cleaning up the dangling image failed. Ignore the 182 // error to not mark the pull as failed. 183 logger.WithError(err).Warn("unexpected error while removing outdated dangling image reference") 184 } 185 186 i.LogImageEvent(reference.FamiliarString(ref), reference.FamiliarName(ref), events.ActionPull) 187 outNewImg = &img 188 return nil 189 } 190 191 // writeStatus writes a status message to out. If newerDownloaded is true, the 192 // status message indicates that a newer image was downloaded. Otherwise, it 193 // indicates that the image is up to date. requestedTag is the tag the message 194 // will refer to. 195 func writeStatus(out progress.Output, requestedTag string, newerDownloaded bool) { 196 if newerDownloaded { 197 progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag) 198 } else { 199 progress.Message(out, "", "Status: Image is up to date for "+requestedTag) 200 } 201 }