github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/distribution/pull.go (about) 1 package distribution // import "github.com/docker/docker/distribution" 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/docker/distribution/reference" 8 "github.com/docker/docker/api" 9 "github.com/docker/docker/distribution/metadata" 10 "github.com/docker/docker/pkg/progress" 11 refstore "github.com/docker/docker/reference" 12 "github.com/docker/docker/registry" 13 digest "github.com/opencontainers/go-digest" 14 specs "github.com/opencontainers/image-spec/specs-go/v1" 15 "github.com/pkg/errors" 16 "github.com/sirupsen/logrus" 17 ) 18 19 // Puller is an interface that abstracts pulling for different API versions. 20 type Puller interface { 21 // Pull tries to pull the image referenced by `tag` 22 // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint. 23 // 24 Pull(ctx context.Context, ref reference.Named, platform *specs.Platform) error 25 } 26 27 // newPuller returns a Puller interface that will pull from either a v1 or v2 28 // registry. The endpoint argument contains a Version field that determines 29 // whether a v1 or v2 puller will be created. The other parameters are passed 30 // through to the underlying puller implementation for use during the actual 31 // pull operation. 32 func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig, local ContentStore) (Puller, error) { 33 switch endpoint.Version { 34 case registry.APIVersion2: 35 return &v2Puller{ 36 V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore), 37 endpoint: endpoint, 38 config: imagePullConfig, 39 repoInfo: repoInfo, 40 manifestStore: &manifestStore{ 41 local: local, 42 }, 43 }, nil 44 case registry.APIVersion1: 45 return nil, fmt.Errorf("protocol version %d no longer supported. Please contact admins of registry %s", endpoint.Version, endpoint.URL) 46 } 47 return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL) 48 } 49 50 // Pull initiates a pull operation. image is the repository name to pull, and 51 // tag may be either empty, or indicate a specific tag to pull. 52 func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullConfig, local ContentStore) error { 53 // Resolve the Repository name from fqn to RepositoryInfo 54 repoInfo, err := imagePullConfig.RegistryService.ResolveRepository(ref) 55 if err != nil { 56 return err 57 } 58 59 // makes sure name is not `scratch` 60 if err := ValidateRepoName(repoInfo.Name); err != nil { 61 return err 62 } 63 64 endpoints, err := imagePullConfig.RegistryService.LookupPullEndpoints(reference.Domain(repoInfo.Name)) 65 if err != nil { 66 return err 67 } 68 69 var ( 70 lastErr error 71 72 // discardNoSupportErrors is used to track whether an endpoint encountered an error of type registry.ErrNoSupport 73 // By default it is false, which means that if an ErrNoSupport error is encountered, it will be saved in lastErr. 74 // As soon as another kind of error is encountered, discardNoSupportErrors is set to true, avoiding the saving of 75 // any subsequent ErrNoSupport errors in lastErr. 76 // It's needed for pull-by-digest on v1 endpoints: if there are only v1 endpoints configured, the error should be 77 // returned and displayed, but if there was a v2 endpoint which supports pull-by-digest, then the last relevant 78 // error is the ones from v2 endpoints not v1. 79 discardNoSupportErrors bool 80 81 // confirmedV2 is set to true if a pull attempt managed to 82 // confirm that it was talking to a v2 registry. This will 83 // prevent fallback to the v1 protocol. 84 confirmedV2 bool 85 86 // confirmedTLSRegistries is a map indicating which registries 87 // are known to be using TLS. There should never be a plaintext 88 // retry for any of these. 89 confirmedTLSRegistries = make(map[string]struct{}) 90 ) 91 for _, endpoint := range endpoints { 92 if imagePullConfig.RequireSchema2 && endpoint.Version == registry.APIVersion1 { 93 continue 94 } 95 96 if confirmedV2 && endpoint.Version == registry.APIVersion1 { 97 logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL) 98 continue 99 } 100 101 if endpoint.URL.Scheme != "https" { 102 if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { 103 logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) 104 continue 105 } 106 } 107 108 logrus.Debugf("Trying to pull %s from %s %s", reference.FamiliarName(repoInfo.Name), endpoint.URL, endpoint.Version) 109 110 puller, err := newPuller(endpoint, repoInfo, imagePullConfig, local) 111 if err != nil { 112 lastErr = err 113 continue 114 } 115 116 if err := puller.Pull(ctx, ref, imagePullConfig.Platform); err != nil { 117 // Was this pull cancelled? If so, don't try to fall 118 // back. 119 fallback := false 120 select { 121 case <-ctx.Done(): 122 default: 123 if fallbackErr, ok := err.(fallbackError); ok { 124 fallback = true 125 confirmedV2 = confirmedV2 || fallbackErr.confirmedV2 126 if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { 127 confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} 128 } 129 err = fallbackErr.err 130 } 131 } 132 if fallback { 133 if _, ok := err.(ErrNoSupport); !ok { 134 // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors. 135 discardNoSupportErrors = true 136 // append subsequent errors 137 lastErr = err 138 } else if !discardNoSupportErrors { 139 // Save the ErrNoSupport error, because it's either the first error or all encountered errors 140 // were also ErrNoSupport errors. 141 // append subsequent errors 142 lastErr = err 143 } 144 logrus.Infof("Attempting next endpoint for pull after error: %v", err) 145 continue 146 } 147 logrus.Errorf("Not continuing with pull after error: %v", err) 148 return TranslatePullError(err, ref) 149 } 150 151 imagePullConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "pull") 152 return nil 153 } 154 155 if lastErr == nil { 156 lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref)) 157 } 158 159 return TranslatePullError(lastErr, ref) 160 } 161 162 // writeStatus writes a status message to out. If layersDownloaded is true, the 163 // status message indicates that a newer image was downloaded. Otherwise, it 164 // indicates that the image is up to date. requestedTag is the tag the message 165 // will refer to. 166 func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) { 167 if layersDownloaded { 168 progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag) 169 } else { 170 progress.Message(out, "", "Status: Image is up to date for "+requestedTag) 171 } 172 } 173 174 // ValidateRepoName validates the name of a repository. 175 func ValidateRepoName(name reference.Named) error { 176 if reference.FamiliarName(name) == api.NoBaseImageSpecifier { 177 return errors.WithStack(reservedNameError(api.NoBaseImageSpecifier)) 178 } 179 return nil 180 } 181 182 func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error { 183 dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst) 184 if err != nil { 185 return err 186 } 187 188 if oldTagID, err := store.Get(dgstRef); err == nil { 189 if oldTagID != id { 190 // Updating digests not supported by reference store 191 logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id) 192 } 193 return nil 194 } else if err != refstore.ErrDoesNotExist { 195 return err 196 } 197 198 return store.AddDigest(dgstRef, id, true) 199 }