github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/distribution/push_v1.go (about) 1 package distribution // import "github.com/docker/docker/distribution" 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 8 "github.com/docker/distribution/reference" 9 "github.com/docker/distribution/registry/client/transport" 10 "github.com/docker/docker/distribution/metadata" 11 "github.com/docker/docker/dockerversion" 12 "github.com/docker/docker/image" 13 "github.com/docker/docker/image/v1" 14 "github.com/docker/docker/layer" 15 "github.com/docker/docker/pkg/ioutils" 16 "github.com/docker/docker/pkg/progress" 17 "github.com/docker/docker/pkg/stringid" 18 "github.com/docker/docker/pkg/system" 19 "github.com/docker/docker/registry" 20 "github.com/opencontainers/go-digest" 21 "github.com/sirupsen/logrus" 22 ) 23 24 type v1Pusher struct { 25 v1IDService *metadata.V1IDService 26 endpoint registry.APIEndpoint 27 ref reference.Named 28 repoInfo *registry.RepositoryInfo 29 config *ImagePushConfig 30 session *registry.Session 31 } 32 33 func (p *v1Pusher) Push(ctx context.Context) error { 34 tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name) 35 if err != nil { 36 return err 37 } 38 // Adds Docker-specific headers as well as user-specified headers (metaHeaders) 39 tr := transport.NewTransport( 40 // TODO(tiborvass): was NoTimeout 41 registry.NewTransport(tlsConfig), 42 registry.Headers(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)..., 43 ) 44 client := registry.HTTPClient(tr) 45 v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders) 46 p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint) 47 if err != nil { 48 // TODO(dmcgowan): Check if should fallback 49 return fallbackError{err: err} 50 } 51 if err := p.pushRepository(ctx); err != nil { 52 // TODO(dmcgowan): Check if should fallback 53 return err 54 } 55 return nil 56 } 57 58 // v1Image exposes the configuration, filesystem layer ID, and a v1 ID for an 59 // image being pushed to a v1 registry. 60 type v1Image interface { 61 Config() []byte 62 Layer() layer.Layer 63 V1ID() string 64 } 65 66 type v1ImageCommon struct { 67 layer layer.Layer 68 config []byte 69 v1ID string 70 } 71 72 func (common *v1ImageCommon) Config() []byte { 73 return common.config 74 } 75 76 func (common *v1ImageCommon) V1ID() string { 77 return common.v1ID 78 } 79 80 func (common *v1ImageCommon) Layer() layer.Layer { 81 return common.layer 82 } 83 84 // v1TopImage defines a runnable (top layer) image being pushed to a v1 85 // registry. 86 type v1TopImage struct { 87 v1ImageCommon 88 imageID image.ID 89 } 90 91 func newV1TopImage(imageID image.ID, img *image.Image, l layer.Layer, parent *v1DependencyImage) (*v1TopImage, error) { 92 v1ID := imageID.Digest().Hex() 93 parentV1ID := "" 94 if parent != nil { 95 parentV1ID = parent.V1ID() 96 } 97 98 config, err := v1.MakeV1ConfigFromConfig(img, v1ID, parentV1ID, false) 99 if err != nil { 100 return nil, err 101 } 102 103 return &v1TopImage{ 104 v1ImageCommon: v1ImageCommon{ 105 v1ID: v1ID, 106 config: config, 107 layer: l, 108 }, 109 imageID: imageID, 110 }, nil 111 } 112 113 // v1DependencyImage defines a dependency layer being pushed to a v1 registry. 114 type v1DependencyImage struct { 115 v1ImageCommon 116 } 117 118 func newV1DependencyImage(l layer.Layer, parent *v1DependencyImage) *v1DependencyImage { 119 v1ID := digest.Digest(l.ChainID()).Hex() 120 121 var config string 122 if parent != nil { 123 config = fmt.Sprintf(`{"id":"%s","parent":"%s"}`, v1ID, parent.V1ID()) 124 } else { 125 config = fmt.Sprintf(`{"id":"%s"}`, v1ID) 126 } 127 return &v1DependencyImage{ 128 v1ImageCommon: v1ImageCommon{ 129 v1ID: v1ID, 130 config: []byte(config), 131 layer: l, 132 }, 133 } 134 } 135 136 // Retrieve the all the images to be uploaded in the correct order 137 func (p *v1Pusher) getImageList() (imageList []v1Image, tagsByImage map[image.ID][]string, referencedLayers []PushLayer, err error) { 138 tagsByImage = make(map[image.ID][]string) 139 140 // Ignore digest references 141 if _, isCanonical := p.ref.(reference.Canonical); isCanonical { 142 return 143 } 144 145 tagged, isTagged := p.ref.(reference.NamedTagged) 146 if isTagged { 147 // Push a specific tag 148 var imgID image.ID 149 var dgst digest.Digest 150 dgst, err = p.config.ReferenceStore.Get(p.ref) 151 if err != nil { 152 return 153 } 154 imgID = image.IDFromDigest(dgst) 155 156 imageList, err = p.imageListForTag(imgID, nil, &referencedLayers) 157 if err != nil { 158 return 159 } 160 161 tagsByImage[imgID] = []string{tagged.Tag()} 162 163 return 164 } 165 166 imagesSeen := make(map[digest.Digest]struct{}) 167 dependenciesSeen := make(map[layer.ChainID]*v1DependencyImage) 168 169 associations := p.config.ReferenceStore.ReferencesByName(p.ref) 170 for _, association := range associations { 171 if tagged, isTagged = association.Ref.(reference.NamedTagged); !isTagged { 172 // Ignore digest references. 173 continue 174 } 175 176 imgID := image.IDFromDigest(association.ID) 177 tagsByImage[imgID] = append(tagsByImage[imgID], tagged.Tag()) 178 179 if _, present := imagesSeen[association.ID]; present { 180 // Skip generating image list for already-seen image 181 continue 182 } 183 imagesSeen[association.ID] = struct{}{} 184 185 imageListForThisTag, err := p.imageListForTag(imgID, dependenciesSeen, &referencedLayers) 186 if err != nil { 187 return nil, nil, nil, err 188 } 189 190 // append to main image list 191 imageList = append(imageList, imageListForThisTag...) 192 } 193 if len(imageList) == 0 { 194 return nil, nil, nil, fmt.Errorf("No images found for the requested repository / tag") 195 } 196 logrus.Debugf("Image list: %v", imageList) 197 logrus.Debugf("Tags by image: %v", tagsByImage) 198 199 return 200 } 201 202 func (p *v1Pusher) imageListForTag(imgID image.ID, dependenciesSeen map[layer.ChainID]*v1DependencyImage, referencedLayers *[]PushLayer) (imageListForThisTag []v1Image, err error) { 203 ics, ok := p.config.ImageStore.(*imageConfigStore) 204 if !ok { 205 return nil, fmt.Errorf("only image store images supported for v1 push") 206 } 207 img, err := ics.Store.Get(imgID) 208 if err != nil { 209 return nil, err 210 } 211 212 topLayerID := img.RootFS.ChainID() 213 214 if !system.IsOSSupported(img.OperatingSystem()) { 215 return nil, system.ErrNotSupportedOperatingSystem 216 } 217 pl, err := p.config.LayerStores[img.OperatingSystem()].Get(topLayerID) 218 *referencedLayers = append(*referencedLayers, pl) 219 if err != nil { 220 return nil, fmt.Errorf("failed to get top layer from image: %v", err) 221 } 222 223 // V1 push is deprecated, only support existing layerstore layers 224 lsl, ok := pl.(*storeLayer) 225 if !ok { 226 return nil, fmt.Errorf("only layer store layers supported for v1 push") 227 } 228 l := lsl.Layer 229 230 dependencyImages, parent := generateDependencyImages(l.Parent(), dependenciesSeen) 231 232 topImage, err := newV1TopImage(imgID, img, l, parent) 233 if err != nil { 234 return nil, err 235 } 236 237 imageListForThisTag = append(dependencyImages, topImage) 238 239 return 240 } 241 242 func generateDependencyImages(l layer.Layer, dependenciesSeen map[layer.ChainID]*v1DependencyImage) (imageListForThisTag []v1Image, parent *v1DependencyImage) { 243 if l == nil { 244 return nil, nil 245 } 246 247 imageListForThisTag, parent = generateDependencyImages(l.Parent(), dependenciesSeen) 248 249 if dependenciesSeen != nil { 250 if dependencyImage, present := dependenciesSeen[l.ChainID()]; present { 251 // This layer is already on the list, we can ignore it 252 // and all its parents. 253 return imageListForThisTag, dependencyImage 254 } 255 } 256 257 dependencyImage := newV1DependencyImage(l, parent) 258 imageListForThisTag = append(imageListForThisTag, dependencyImage) 259 260 if dependenciesSeen != nil { 261 dependenciesSeen[l.ChainID()] = dependencyImage 262 } 263 264 return imageListForThisTag, dependencyImage 265 } 266 267 // createImageIndex returns an index of an image's layer IDs and tags. 268 func createImageIndex(images []v1Image, tags map[image.ID][]string) []*registry.ImgData { 269 var imageIndex []*registry.ImgData 270 for _, img := range images { 271 v1ID := img.V1ID() 272 273 if topImage, isTopImage := img.(*v1TopImage); isTopImage { 274 if tags, hasTags := tags[topImage.imageID]; hasTags { 275 // If an image has tags you must add an entry in the image index 276 // for each tag 277 for _, tag := range tags { 278 imageIndex = append(imageIndex, ®istry.ImgData{ 279 ID: v1ID, 280 Tag: tag, 281 }) 282 } 283 continue 284 } 285 } 286 287 // If the image does not have a tag it still needs to be sent to the 288 // registry with an empty tag so that it is associated with the repository 289 imageIndex = append(imageIndex, ®istry.ImgData{ 290 ID: v1ID, 291 Tag: "", 292 }) 293 } 294 return imageIndex 295 } 296 297 // lookupImageOnEndpoint checks the specified endpoint to see if an image exists 298 // and if it is absent then it sends the image id to the channel to be pushed. 299 func (p *v1Pusher) lookupImageOnEndpoint(wg *sync.WaitGroup, endpoint string, images chan v1Image, imagesToPush chan string) { 300 defer wg.Done() 301 for image := range images { 302 v1ID := image.V1ID() 303 truncID := stringid.TruncateID(image.Layer().DiffID().String()) 304 if err := p.session.LookupRemoteImage(v1ID, endpoint); err != nil { 305 logrus.Errorf("Error in LookupRemoteImage: %s", err) 306 imagesToPush <- v1ID 307 progress.Update(p.config.ProgressOutput, truncID, "Waiting") 308 } else { 309 progress.Update(p.config.ProgressOutput, truncID, "Already exists") 310 } 311 } 312 } 313 314 func (p *v1Pusher) pushImageToEndpoint(ctx context.Context, endpoint string, imageList []v1Image, tags map[image.ID][]string, repo *registry.RepositoryData) error { 315 workerCount := len(imageList) 316 // start a maximum of 5 workers to check if images exist on the specified endpoint. 317 if workerCount > 5 { 318 workerCount = 5 319 } 320 var ( 321 wg = &sync.WaitGroup{} 322 imageData = make(chan v1Image, workerCount*2) 323 imagesToPush = make(chan string, workerCount*2) 324 pushes = make(chan map[string]struct{}, 1) 325 ) 326 for i := 0; i < workerCount; i++ { 327 wg.Add(1) 328 go p.lookupImageOnEndpoint(wg, endpoint, imageData, imagesToPush) 329 } 330 // start a go routine that consumes the images to push 331 go func() { 332 shouldPush := make(map[string]struct{}) 333 for id := range imagesToPush { 334 shouldPush[id] = struct{}{} 335 } 336 pushes <- shouldPush 337 }() 338 for _, v1Image := range imageList { 339 imageData <- v1Image 340 } 341 // close the channel to notify the workers that there will be no more images to check. 342 close(imageData) 343 wg.Wait() 344 close(imagesToPush) 345 // wait for all the images that require pushes to be collected into a consumable map. 346 shouldPush := <-pushes 347 // finish by pushing any images and tags to the endpoint. The order that the images are pushed 348 // is very important that is why we are still iterating over the ordered list of imageIDs. 349 for _, img := range imageList { 350 v1ID := img.V1ID() 351 if _, push := shouldPush[v1ID]; push { 352 if _, err := p.pushImage(ctx, img, endpoint); err != nil { 353 // FIXME: Continue on error? 354 return err 355 } 356 } 357 if topImage, isTopImage := img.(*v1TopImage); isTopImage { 358 for _, tag := range tags[topImage.imageID] { 359 progress.Messagef(p.config.ProgressOutput, "", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(v1ID), endpoint+"repositories/"+reference.Path(p.repoInfo.Name)+"/tags/"+tag) 360 if err := p.session.PushRegistryTag(p.repoInfo.Name, v1ID, tag, endpoint); err != nil { 361 return err 362 } 363 } 364 } 365 } 366 return nil 367 } 368 369 // pushRepository pushes layers that do not already exist on the registry. 370 func (p *v1Pusher) pushRepository(ctx context.Context) error { 371 imgList, tags, referencedLayers, err := p.getImageList() 372 defer func() { 373 for _, l := range referencedLayers { 374 l.Release() 375 } 376 }() 377 if err != nil { 378 return err 379 } 380 381 imageIndex := createImageIndex(imgList, tags) 382 for _, data := range imageIndex { 383 logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag) 384 } 385 386 // Register all the images in a repository with the registry 387 // If an image is not in this list it will not be associated with the repository 388 repoData, err := p.session.PushImageJSONIndex(p.repoInfo.Name, imageIndex, false, nil) 389 if err != nil { 390 return err 391 } 392 // push the repository to each of the endpoints only if it does not exist. 393 for _, endpoint := range repoData.Endpoints { 394 if err := p.pushImageToEndpoint(ctx, endpoint, imgList, tags, repoData); err != nil { 395 return err 396 } 397 } 398 _, err = p.session.PushImageJSONIndex(p.repoInfo.Name, imageIndex, true, repoData.Endpoints) 399 return err 400 } 401 402 func (p *v1Pusher) pushImage(ctx context.Context, v1Image v1Image, ep string) (checksum string, err error) { 403 l := v1Image.Layer() 404 v1ID := v1Image.V1ID() 405 truncID := stringid.TruncateID(l.DiffID().String()) 406 407 jsonRaw := v1Image.Config() 408 progress.Update(p.config.ProgressOutput, truncID, "Pushing") 409 410 // General rule is to use ID for graph accesses and compatibilityID for 411 // calls to session.registry() 412 imgData := ®istry.ImgData{ 413 ID: v1ID, 414 } 415 416 // Send the json 417 if err := p.session.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil { 418 if err == registry.ErrAlreadyExists { 419 progress.Update(p.config.ProgressOutput, truncID, "Image already pushed, skipping") 420 return "", nil 421 } 422 return "", err 423 } 424 425 arch, err := l.TarStream() 426 if err != nil { 427 return "", err 428 } 429 defer arch.Close() 430 431 // don't care if this fails; best effort 432 size, _ := l.DiffSize() 433 434 // Send the layer 435 logrus.Debugf("rendered layer for %s of [%d] size", v1ID, size) 436 437 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), p.config.ProgressOutput, size, truncID, "Pushing") 438 defer reader.Close() 439 440 checksum, checksumPayload, err := p.session.PushImageLayerRegistry(v1ID, reader, ep, jsonRaw) 441 if err != nil { 442 return "", err 443 } 444 imgData.Checksum = checksum 445 imgData.ChecksumPayload = checksumPayload 446 // Send the checksum 447 if err := p.session.PushImageChecksumRegistry(imgData, ep); err != nil { 448 return "", err 449 } 450 451 if err := p.v1IDService.Set(v1ID, p.repoInfo.Index.Name, l.DiffID()); err != nil { 452 logrus.Warnf("Could not set v1 ID mapping: %v", err) 453 } 454 455 progress.Update(p.config.ProgressOutput, truncID, "Image successfully pushed") 456 return imgData.Checksum, nil 457 }