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  }