github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/distribution/pull.go (about)

     1  package distribution // import "github.com/docker/docker/distribution"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/containerd/log"
     8  	"github.com/distribution/reference"
     9  	"github.com/docker/docker/api"
    10  	"github.com/docker/docker/api/types/events"
    11  	refstore "github.com/docker/docker/reference"
    12  	"github.com/docker/docker/registry"
    13  	"github.com/opencontainers/go-digest"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // Pull initiates a pull operation. image is the repository name to pull, and
    18  // tag may be either empty, or indicate a specific tag to pull.
    19  func Pull(ctx context.Context, ref reference.Named, config *ImagePullConfig, local ContentStore) error {
    20  	repoInfo, err := pullEndpoints(ctx, config.RegistryService, ref, func(ctx context.Context, repoInfo registry.RepositoryInfo, endpoint registry.APIEndpoint) error {
    21  		log.G(ctx).Debugf("Trying to pull %s from %s", reference.FamiliarName(repoInfo.Name), endpoint.URL)
    22  		puller := newPuller(endpoint, &repoInfo, config, local)
    23  		return puller.pull(ctx, ref)
    24  	})
    25  
    26  	if err == nil {
    27  		config.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), events.ActionPull)
    28  	}
    29  
    30  	return err
    31  }
    32  
    33  // Tags returns available tags for the given image in the remote repository.
    34  func Tags(ctx context.Context, ref reference.Named, config *Config) ([]string, error) {
    35  	var tags []string
    36  	_, err := pullEndpoints(ctx, config.RegistryService, ref, func(ctx context.Context, repoInfo registry.RepositoryInfo, endpoint registry.APIEndpoint) error {
    37  		repo, err := newRepository(ctx, &repoInfo, endpoint, config.MetaHeaders, config.AuthConfig, "pull")
    38  		if err != nil {
    39  			return err
    40  		}
    41  
    42  		tags, err = repo.Tags(ctx).All(ctx)
    43  		return err
    44  	})
    45  
    46  	return tags, err
    47  }
    48  
    49  // validateRepoName validates the name of a repository.
    50  func validateRepoName(name reference.Named) error {
    51  	if reference.FamiliarName(name) == api.NoBaseImageSpecifier {
    52  		return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier))
    53  	}
    54  	return nil
    55  }
    56  
    57  func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error {
    58  	dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst)
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	if oldTagID, err := store.Get(dgstRef); err == nil {
    64  		if oldTagID != id {
    65  			// Updating digests not supported by reference store
    66  			log.G(context.TODO()).Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id)
    67  		}
    68  		return nil
    69  	} else if err != refstore.ErrDoesNotExist {
    70  		return err
    71  	}
    72  
    73  	return store.AddDigest(dgstRef, id, true)
    74  }
    75  
    76  func pullEndpoints(ctx context.Context, registryService RegistryResolver, ref reference.Named,
    77  	f func(context.Context, registry.RepositoryInfo, registry.APIEndpoint) error,
    78  ) (*registry.RepositoryInfo, error) {
    79  	// Resolve the Repository name from fqn to RepositoryInfo
    80  	repoInfo, err := registryService.ResolveRepository(ref)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	// makes sure name is not `scratch`
    86  	if err := validateRepoName(repoInfo.Name); err != nil {
    87  		return repoInfo, err
    88  	}
    89  
    90  	endpoints, err := registryService.LookupPullEndpoints(reference.Domain(repoInfo.Name))
    91  	if err != nil {
    92  		return repoInfo, err
    93  	}
    94  
    95  	var (
    96  		lastErr error
    97  
    98  		// confirmedTLSRegistries is a map indicating which registries
    99  		// are known to be using TLS. There should never be a plaintext
   100  		// retry for any of these.
   101  		confirmedTLSRegistries = make(map[string]struct{})
   102  	)
   103  	for _, endpoint := range endpoints {
   104  		if endpoint.URL.Scheme != "https" {
   105  			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
   106  				log.G(ctx).Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
   107  				continue
   108  			}
   109  		}
   110  
   111  		log.G(ctx).Debugf("Trying to pull %s from %s", reference.FamiliarName(repoInfo.Name), endpoint.URL)
   112  
   113  		if err := f(ctx, *repoInfo, endpoint); err != nil {
   114  			if _, ok := err.(fallbackError); !ok && continueOnError(err, endpoint.Mirror) {
   115  				err = fallbackError{
   116  					err:         err,
   117  					transportOK: true,
   118  				}
   119  			}
   120  
   121  			// Was this pull cancelled? If so, don't try to fall
   122  			// back.
   123  			fallback := false
   124  			select {
   125  			case <-ctx.Done():
   126  			default:
   127  				if fallbackErr, ok := err.(fallbackError); ok {
   128  					fallback = true
   129  					if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
   130  						confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
   131  					}
   132  					err = fallbackErr.err
   133  				}
   134  			}
   135  			if fallback {
   136  				lastErr = err
   137  				log.G(ctx).Infof("Attempting next endpoint for pull after error: %v", err)
   138  				continue
   139  			}
   140  			log.G(ctx).Errorf("Not continuing with pull after error: %v", err)
   141  			return repoInfo, translatePullError(err, ref)
   142  		}
   143  
   144  		return repoInfo, nil
   145  	}
   146  
   147  	if lastErr == nil {
   148  		lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref))
   149  	}
   150  
   151  	return repoInfo, translatePullError(lastErr, ref)
   152  }