github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/daemon/containerd/image_pull.go (about)

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