github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/distribution/pull.go (about) 1 package distribution 2 3 import ( 4 "fmt" 5 6 "github.com/Sirupsen/logrus" 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 "github.com/opencontainers/go-digest" 14 "golang.org/x/net/context" 15 ) 16 17 // Puller is an interface that abstracts pulling for different API versions. 18 type Puller interface { 19 // Pull tries to pull the image referenced by `tag` 20 // Pull returns an error if any, as well as a boolean that determines whether to retry Pull on the next configured endpoint. 21 // 22 Pull(ctx context.Context, ref reference.Named) error 23 } 24 25 // newPuller returns a Puller interface that will pull from either a v1 or v2 26 // registry. The endpoint argument contains a Version field that determines 27 // whether a v1 or v2 puller will be created. The other parameters are passed 28 // through to the underlying puller implementation for use during the actual 29 // pull operation. 30 func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePullConfig *ImagePullConfig) (Puller, error) { 31 switch endpoint.Version { 32 case registry.APIVersion2: 33 return &v2Puller{ 34 V2MetadataService: metadata.NewV2MetadataService(imagePullConfig.MetadataStore), 35 endpoint: endpoint, 36 config: imagePullConfig, 37 repoInfo: repoInfo, 38 }, nil 39 case registry.APIVersion1: 40 return &v1Puller{ 41 v1IDService: metadata.NewV1IDService(imagePullConfig.MetadataStore), 42 endpoint: endpoint, 43 config: imagePullConfig, 44 repoInfo: repoInfo, 45 }, nil 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) 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) 111 if err != nil { 112 lastErr = err 113 continue 114 } 115 if err := puller.Pull(ctx, ref); err != nil { 116 // Was this pull cancelled? If so, don't try to fall 117 // back. 118 fallback := false 119 select { 120 case <-ctx.Done(): 121 default: 122 if fallbackErr, ok := err.(fallbackError); ok { 123 fallback = true 124 confirmedV2 = confirmedV2 || fallbackErr.confirmedV2 125 if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { 126 confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} 127 } 128 err = fallbackErr.err 129 } 130 } 131 if fallback { 132 if _, ok := err.(ErrNoSupport); !ok { 133 // Because we found an error that's not ErrNoSupport, discard all subsequent ErrNoSupport errors. 134 discardNoSupportErrors = true 135 // append subsequent errors 136 lastErr = err 137 } else if !discardNoSupportErrors { 138 // Save the ErrNoSupport error, because it's either the first error or all encountered errors 139 // were also ErrNoSupport errors. 140 // append subsequent errors 141 lastErr = err 142 } 143 logrus.Errorf("Attempting next endpoint for pull after error: %v", err) 144 continue 145 } 146 logrus.Errorf("Not continuing with pull after error: %v", err) 147 return TranslatePullError(err, ref) 148 } 149 150 imagePullConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "pull") 151 return nil 152 } 153 154 if lastErr == nil { 155 lastErr = fmt.Errorf("no endpoints found for %s", reference.FamiliarString(ref)) 156 } 157 158 return TranslatePullError(lastErr, ref) 159 } 160 161 // writeStatus writes a status message to out. If layersDownloaded is true, the 162 // status message indicates that a newer image was downloaded. Otherwise, it 163 // indicates that the image is up to date. requestedTag is the tag the message 164 // will refer to. 165 func writeStatus(requestedTag string, out progress.Output, layersDownloaded bool) { 166 if layersDownloaded { 167 progress.Message(out, "", "Status: Downloaded newer image for "+requestedTag) 168 } else { 169 progress.Message(out, "", "Status: Image is up to date for "+requestedTag) 170 } 171 } 172 173 // ValidateRepoName validates the name of a repository. 174 func ValidateRepoName(name reference.Named) error { 175 if reference.FamiliarName(name) == api.NoBaseImageSpecifier { 176 return fmt.Errorf("'%s' is a reserved name", api.NoBaseImageSpecifier) 177 } 178 return nil 179 } 180 181 func addDigestReference(store refstore.Store, ref reference.Named, dgst digest.Digest, id digest.Digest) error { 182 dgstRef, err := reference.WithDigest(reference.TrimNamed(ref), dgst) 183 if err != nil { 184 return err 185 } 186 187 if oldTagID, err := store.Get(dgstRef); err == nil { 188 if oldTagID != id { 189 // Updating digests not supported by reference store 190 logrus.Errorf("Image ID for digest %s changed from %s to %s, cannot update", dgst.String(), oldTagID, id) 191 } 192 return nil 193 } else if err != refstore.ErrDoesNotExist { 194 return err 195 } 196 197 return store.AddDigest(dgstRef, id, true) 198 }