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