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