github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/distribution/pull_v1.go (about) 1 package distribution 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net" 9 "net/url" 10 "os" 11 "strings" 12 "time" 13 14 "github.com/docker/distribution/reference" 15 "github.com/docker/distribution/registry/client/auth" 16 "github.com/docker/distribution/registry/client/transport" 17 "github.com/docker/docker/distribution/metadata" 18 "github.com/docker/docker/distribution/xfer" 19 "github.com/docker/docker/dockerversion" 20 "github.com/docker/docker/image" 21 "github.com/docker/docker/image/v1" 22 "github.com/docker/docker/layer" 23 "github.com/docker/docker/pkg/ioutils" 24 "github.com/docker/docker/pkg/progress" 25 "github.com/docker/docker/pkg/stringid" 26 "github.com/docker/docker/registry" 27 "github.com/sirupsen/logrus" 28 "golang.org/x/net/context" 29 ) 30 31 type v1Puller struct { 32 v1IDService *metadata.V1IDService 33 endpoint registry.APIEndpoint 34 config *ImagePullConfig 35 repoInfo *registry.RepositoryInfo 36 session *registry.Session 37 } 38 39 func (p *v1Puller) Pull(ctx context.Context, ref reference.Named, platform string) error { 40 if _, isCanonical := ref.(reference.Canonical); isCanonical { 41 // Allowing fallback, because HTTPS v1 is before HTTP v2 42 return fallbackError{err: ErrNoSupport{Err: errors.New("Cannot pull by digest with v1 registry")}} 43 } 44 45 tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name) 46 if err != nil { 47 return err 48 } 49 // Adds Docker-specific headers as well as user-specified headers (metaHeaders) 50 tr := transport.NewTransport( 51 // TODO(tiborvass): was ReceiveTimeout 52 registry.NewTransport(tlsConfig), 53 registry.Headers(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)..., 54 ) 55 client := registry.HTTPClient(tr) 56 v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders) 57 p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint) 58 if err != nil { 59 // TODO(dmcgowan): Check if should fallback 60 logrus.Debugf("Fallback from error: %s", err) 61 return fallbackError{err: err} 62 } 63 if err := p.pullRepository(ctx, ref); err != nil { 64 // TODO(dmcgowan): Check if should fallback 65 return err 66 } 67 progress.Message(p.config.ProgressOutput, "", p.repoInfo.Name.Name()+": this image was pulled from a legacy registry. Important: This registry version will not be supported in future versions of docker.") 68 69 return nil 70 } 71 72 // Note use auth.Scope rather than reference.Named due to this warning causing Jenkins CI to fail: 73 // warning: ref can be github.com/docker/docker/vendor/github.com/docker/distribution/registry/client/auth.Scope (interfacer) 74 func (p *v1Puller) pullRepository(ctx context.Context, ref auth.Scope) error { 75 progress.Message(p.config.ProgressOutput, "", "Pulling repository "+p.repoInfo.Name.Name()) 76 77 tagged, isTagged := ref.(reference.NamedTagged) 78 79 repoData, err := p.session.GetRepositoryData(p.repoInfo.Name) 80 if err != nil { 81 if strings.Contains(err.Error(), "HTTP code: 404") { 82 if isTagged { 83 return fmt.Errorf("Error: image %s:%s not found", reference.Path(p.repoInfo.Name), tagged.Tag()) 84 } 85 return fmt.Errorf("Error: image %s not found", reference.Path(p.repoInfo.Name)) 86 } 87 // Unexpected HTTP error 88 return err 89 } 90 91 logrus.Debug("Retrieving the tag list") 92 var tagsList map[string]string 93 if !isTagged { 94 tagsList, err = p.session.GetRemoteTags(repoData.Endpoints, p.repoInfo.Name) 95 } else { 96 var tagID string 97 tagsList = make(map[string]string) 98 tagID, err = p.session.GetRemoteTag(repoData.Endpoints, p.repoInfo.Name, tagged.Tag()) 99 if err == registry.ErrRepoNotFound { 100 return fmt.Errorf("Tag %s not found in repository %s", tagged.Tag(), p.repoInfo.Name.Name()) 101 } 102 tagsList[tagged.Tag()] = tagID 103 } 104 if err != nil { 105 logrus.Errorf("unable to get remote tags: %s", err) 106 return err 107 } 108 109 for tag, id := range tagsList { 110 repoData.ImgList[id] = ®istry.ImgData{ 111 ID: id, 112 Tag: tag, 113 Checksum: "", 114 } 115 } 116 117 layersDownloaded := false 118 for _, imgData := range repoData.ImgList { 119 if isTagged && imgData.Tag != tagged.Tag() { 120 continue 121 } 122 123 err := p.downloadImage(ctx, repoData, imgData, &layersDownloaded) 124 if err != nil { 125 return err 126 } 127 } 128 129 writeStatus(reference.FamiliarString(ref), p.config.ProgressOutput, layersDownloaded) 130 return nil 131 } 132 133 func (p *v1Puller) downloadImage(ctx context.Context, repoData *registry.RepositoryData, img *registry.ImgData, layersDownloaded *bool) error { 134 if img.Tag == "" { 135 logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) 136 return nil 137 } 138 139 localNameRef, err := reference.WithTag(p.repoInfo.Name, img.Tag) 140 if err != nil { 141 retErr := fmt.Errorf("Image (id: %s) has invalid tag: %s", img.ID, img.Tag) 142 logrus.Debug(retErr.Error()) 143 return retErr 144 } 145 146 if err := v1.ValidateID(img.ID); err != nil { 147 return err 148 } 149 150 progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s", img.Tag, p.repoInfo.Name.Name()) 151 success := false 152 var lastErr error 153 for _, ep := range p.repoInfo.Index.Mirrors { 154 ep += "v1/" 155 progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, p.repoInfo.Name.Name(), ep)) 156 if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { 157 // Don't report errors when pulling from mirrors. 158 logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, p.repoInfo.Name.Name(), ep, err) 159 continue 160 } 161 success = true 162 break 163 } 164 if !success { 165 for _, ep := range repoData.Endpoints { 166 progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Pulling image (%s) from %s, endpoint: %s", img.Tag, p.repoInfo.Name.Name(), ep) 167 if err = p.pullImage(ctx, img.ID, ep, localNameRef, layersDownloaded); err != nil { 168 // It's not ideal that only the last error is returned, it would be better to concatenate the errors. 169 // As the error is also given to the output stream the user will see the error. 170 lastErr = err 171 progress.Updatef(p.config.ProgressOutput, stringid.TruncateID(img.ID), "Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, p.repoInfo.Name.Name(), ep, err) 172 continue 173 } 174 success = true 175 break 176 } 177 } 178 if !success { 179 err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, p.repoInfo.Name.Name(), lastErr) 180 progress.Update(p.config.ProgressOutput, stringid.TruncateID(img.ID), err.Error()) 181 return err 182 } 183 return nil 184 } 185 186 func (p *v1Puller) pullImage(ctx context.Context, v1ID, endpoint string, localNameRef reference.Named, layersDownloaded *bool) (err error) { 187 var history []string 188 history, err = p.session.GetRemoteHistory(v1ID, endpoint) 189 if err != nil { 190 return err 191 } 192 if len(history) < 1 { 193 return fmt.Errorf("empty history for image %s", v1ID) 194 } 195 progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1ID), "Pulling dependent layers") 196 197 var ( 198 descriptors []xfer.DownloadDescriptor 199 newHistory []image.History 200 imgJSON []byte 201 imgSize int64 202 ) 203 204 // Iterate over layers, in order from bottom-most to top-most. Download 205 // config for all layers and create descriptors. 206 for i := len(history) - 1; i >= 0; i-- { 207 v1LayerID := history[i] 208 imgJSON, imgSize, err = p.downloadLayerConfig(v1LayerID, endpoint) 209 if err != nil { 210 return err 211 } 212 213 // Create a new-style config from the legacy configs 214 h, err := v1.HistoryFromConfig(imgJSON, false) 215 if err != nil { 216 return err 217 } 218 newHistory = append(newHistory, h) 219 220 layerDescriptor := &v1LayerDescriptor{ 221 v1LayerID: v1LayerID, 222 indexName: p.repoInfo.Index.Name, 223 endpoint: endpoint, 224 v1IDService: p.v1IDService, 225 layersDownloaded: layersDownloaded, 226 layerSize: imgSize, 227 session: p.session, 228 } 229 230 descriptors = append(descriptors, layerDescriptor) 231 } 232 233 rootFS := image.NewRootFS() 234 resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, "", descriptors, p.config.ProgressOutput) 235 if err != nil { 236 return err 237 } 238 defer release() 239 240 config, err := v1.MakeConfigFromV1Config(imgJSON, &resultRootFS, newHistory) 241 if err != nil { 242 return err 243 } 244 245 imageID, err := p.config.ImageStore.Put(config) 246 if err != nil { 247 return err 248 } 249 250 if p.config.ReferenceStore != nil { 251 if err := p.config.ReferenceStore.AddTag(localNameRef, imageID, true); err != nil { 252 return err 253 } 254 } 255 256 return nil 257 } 258 259 func (p *v1Puller) downloadLayerConfig(v1LayerID, endpoint string) (imgJSON []byte, imgSize int64, err error) { 260 progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1LayerID), "Pulling metadata") 261 262 retries := 5 263 for j := 1; j <= retries; j++ { 264 imgJSON, imgSize, err := p.session.GetRemoteImageJSON(v1LayerID, endpoint) 265 if err != nil && j == retries { 266 progress.Update(p.config.ProgressOutput, stringid.TruncateID(v1LayerID), "Error pulling layer metadata") 267 return nil, 0, err 268 } else if err != nil { 269 time.Sleep(time.Duration(j) * 500 * time.Millisecond) 270 continue 271 } 272 273 return imgJSON, imgSize, nil 274 } 275 276 // not reached 277 return nil, 0, nil 278 } 279 280 type v1LayerDescriptor struct { 281 v1LayerID string 282 indexName string 283 endpoint string 284 v1IDService *metadata.V1IDService 285 layersDownloaded *bool 286 layerSize int64 287 session *registry.Session 288 tmpFile *os.File 289 } 290 291 func (ld *v1LayerDescriptor) Key() string { 292 return "v1:" + ld.v1LayerID 293 } 294 295 func (ld *v1LayerDescriptor) ID() string { 296 return stringid.TruncateID(ld.v1LayerID) 297 } 298 299 func (ld *v1LayerDescriptor) DiffID() (layer.DiffID, error) { 300 return ld.v1IDService.Get(ld.v1LayerID, ld.indexName) 301 } 302 303 func (ld *v1LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) { 304 progress.Update(progressOutput, ld.ID(), "Pulling fs layer") 305 layerReader, err := ld.session.GetRemoteImageLayer(ld.v1LayerID, ld.endpoint, ld.layerSize) 306 if err != nil { 307 progress.Update(progressOutput, ld.ID(), "Error pulling dependent layers") 308 if uerr, ok := err.(*url.Error); ok { 309 err = uerr.Err 310 } 311 if terr, ok := err.(net.Error); ok && terr.Timeout() { 312 return nil, 0, err 313 } 314 return nil, 0, xfer.DoNotRetry{Err: err} 315 } 316 *ld.layersDownloaded = true 317 318 ld.tmpFile, err = ioutil.TempFile("", "GetImageBlob") 319 if err != nil { 320 layerReader.Close() 321 return nil, 0, err 322 } 323 324 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerReader), progressOutput, ld.layerSize, ld.ID(), "Downloading") 325 defer reader.Close() 326 327 _, err = io.Copy(ld.tmpFile, reader) 328 if err != nil { 329 ld.Close() 330 return nil, 0, err 331 } 332 333 progress.Update(progressOutput, ld.ID(), "Download complete") 334 335 logrus.Debugf("Downloaded %s to tempfile %s", ld.ID(), ld.tmpFile.Name()) 336 337 ld.tmpFile.Seek(0, 0) 338 339 // hand off the temporary file to the download manager, so it will only 340 // be closed once 341 tmpFile := ld.tmpFile 342 ld.tmpFile = nil 343 344 return ioutils.NewReadCloserWrapper(tmpFile, func() error { 345 tmpFile.Close() 346 err := os.RemoveAll(tmpFile.Name()) 347 if err != nil { 348 logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name()) 349 } 350 return err 351 }), ld.layerSize, nil 352 } 353 354 func (ld *v1LayerDescriptor) Close() { 355 if ld.tmpFile != nil { 356 ld.tmpFile.Close() 357 if err := os.RemoveAll(ld.tmpFile.Name()); err != nil { 358 logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name()) 359 } 360 ld.tmpFile = nil 361 } 362 } 363 364 func (ld *v1LayerDescriptor) Registered(diffID layer.DiffID) { 365 // Cache mapping from this layer's DiffID to the blobsum 366 ld.v1IDService.Set(ld.v1LayerID, ld.indexName, diffID) 367 }