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  }