github.com/moby/docker@v26.1.3+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  	"time"
     7  
     8  	"github.com/containerd/containerd/leases"
     9  	"github.com/containerd/containerd/namespaces"
    10  	"github.com/containerd/log"
    11  	"github.com/distribution/reference"
    12  	"github.com/docker/docker/api/types/backend"
    13  	"github.com/docker/docker/api/types/registry"
    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  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    20  	"github.com/pkg/errors"
    21  )
    22  
    23  // PullImage initiates a pull operation. image is the repository name to pull, and
    24  // tag may be either empty, or indicate a specific tag to pull.
    25  func (i *ImageService) PullImage(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.AuthConfig, outStream io.Writer) error {
    26  	start := time.Now()
    27  
    28  	err := i.pullImageWithReference(ctx, ref, platform, metaHeaders, authConfig, outStream)
    29  	ImageActions.WithValues("pull").UpdateSince(start)
    30  	if err != nil {
    31  		return err
    32  	}
    33  
    34  	if platform != nil {
    35  		// If --platform was specified, check that the image we pulled matches
    36  		// the expected platform. This check is for situations where the image
    37  		// is a single-arch image, in which case (for backward compatibility),
    38  		// we allow the image to have a non-matching architecture. The code
    39  		// below checks for this situation, and returns a warning to the client,
    40  		// as well as logging it to the daemon logs.
    41  		img, err := i.GetImage(ctx, ref.String(), backend.GetImageOpts{Platform: platform})
    42  
    43  		// Note that this is a special case where GetImage returns both an image
    44  		// and an error: https://github.com/docker/docker/blob/v20.10.7/daemon/images/image.go#L175-L183
    45  		if errdefs.IsNotFound(err) && img != nil {
    46  			po := streamformatter.NewJSONProgressOutput(outStream, false)
    47  			progress.Messagef(po, "", `WARNING: %s`, err.Error())
    48  			log.G(ctx).WithError(err).WithField("image", reference.FamiliarName(ref)).Warn("ignoring platform mismatch on single-arch image")
    49  		} else if err != nil {
    50  			return err
    51  		}
    52  	}
    53  
    54  	return nil
    55  }
    56  
    57  func (i *ImageService) pullImageWithReference(ctx context.Context, ref reference.Named, platform *ocispec.Platform, metaHeaders map[string][]string, authConfig *registry.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  		Platform:        platform,
   103  	}
   104  
   105  	err = distribution.Pull(ctx, ref, imagePullConfig, cs)
   106  	close(progressChan)
   107  	<-writesDone
   108  	return err
   109  }
   110  
   111  func tempLease(ctx context.Context, mgr leases.Manager) (context.Context, func(context.Context) error, error) {
   112  	nop := func(context.Context) error { return nil }
   113  	_, ok := leases.FromContext(ctx)
   114  	if ok {
   115  		return ctx, nop, nil
   116  	}
   117  
   118  	// Use an expiration that ensures the lease is cleaned up at some point if there is a crash, SIGKILL, etc.
   119  	opts := []leases.Opt{
   120  		leases.WithRandomID(),
   121  		leases.WithExpiration(24 * time.Hour),
   122  		leases.WithLabels(map[string]string{
   123  			"moby.lease/temporary": time.Now().UTC().Format(time.RFC3339Nano),
   124  		}),
   125  	}
   126  	l, err := mgr.Create(ctx, opts...)
   127  	if err != nil {
   128  		return ctx, nop, errors.Wrap(err, "error creating temporary lease")
   129  	}
   130  
   131  	ctx = leases.WithLease(ctx, l.ID)
   132  	return ctx, func(ctx context.Context) error {
   133  		return mgr.Delete(ctx, l)
   134  	}, nil
   135  }