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