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