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 }