github.com/codemac/docker@v1.2.1-0.20150518222241-6a18412d5b9c/graph/push.go (about) 1 package graph 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "sync" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/docker/cliconfig" 16 "github.com/docker/docker/image" 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/progressreader" 19 "github.com/docker/docker/pkg/streamformatter" 20 "github.com/docker/docker/pkg/stringid" 21 "github.com/docker/docker/registry" 22 "github.com/docker/docker/runconfig" 23 "github.com/docker/docker/utils" 24 "github.com/docker/libtrust" 25 ) 26 27 var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") 28 29 type ImagePushConfig struct { 30 MetaHeaders map[string][]string 31 AuthConfig *cliconfig.AuthConfig 32 Tag string 33 OutStream io.Writer 34 } 35 36 // Retrieve the all the images to be uploaded in the correct order 37 func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { 38 var ( 39 imageList []string 40 imagesSeen = make(map[string]bool) 41 tagsByImage = make(map[string][]string) 42 ) 43 44 for tag, id := range localRepo { 45 if requestedTag != "" && requestedTag != tag { 46 // Include only the requested tag. 47 continue 48 } 49 50 if utils.DigestReference(tag) { 51 // Ignore digest references. 52 continue 53 } 54 55 var imageListForThisTag []string 56 57 tagsByImage[id] = append(tagsByImage[id], tag) 58 59 for img, err := s.graph.Get(id); img != nil; img, err = img.GetParent() { 60 if err != nil { 61 return nil, nil, err 62 } 63 64 if imagesSeen[img.ID] { 65 // This image is already on the list, we can ignore it and all its parents 66 break 67 } 68 69 imagesSeen[img.ID] = true 70 imageListForThisTag = append(imageListForThisTag, img.ID) 71 } 72 73 // reverse the image list for this tag (so the "most"-parent image is first) 74 for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 { 75 imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] 76 } 77 78 // append to main image list 79 imageList = append(imageList, imageListForThisTag...) 80 } 81 if len(imageList) == 0 { 82 return nil, nil, fmt.Errorf("No images found for the requested repository / tag") 83 } 84 logrus.Debugf("Image list: %v", imageList) 85 logrus.Debugf("Tags by image: %v", tagsByImage) 86 87 return imageList, tagsByImage, nil 88 } 89 90 func (s *TagStore) getImageTags(localRepo map[string]string, askedTag string) ([]string, error) { 91 logrus.Debugf("Checking %s against %#v", askedTag, localRepo) 92 if len(askedTag) > 0 { 93 if _, ok := localRepo[askedTag]; !ok || utils.DigestReference(askedTag) { 94 return nil, fmt.Errorf("Tag does not exist: %s", askedTag) 95 } 96 return []string{askedTag}, nil 97 } 98 var tags []string 99 for tag := range localRepo { 100 if !utils.DigestReference(tag) { 101 tags = append(tags, tag) 102 } 103 } 104 return tags, nil 105 } 106 107 // createImageIndex returns an index of an image's layer IDs and tags. 108 func (s *TagStore) createImageIndex(images []string, tags map[string][]string) []*registry.ImgData { 109 var imageIndex []*registry.ImgData 110 for _, id := range images { 111 if tags, hasTags := tags[id]; hasTags { 112 // If an image has tags you must add an entry in the image index 113 // for each tag 114 for _, tag := range tags { 115 imageIndex = append(imageIndex, ®istry.ImgData{ 116 ID: id, 117 Tag: tag, 118 }) 119 } 120 continue 121 } 122 // If the image does not have a tag it still needs to be sent to the 123 // registry with an empty tag so that it is accociated with the repository 124 imageIndex = append(imageIndex, ®istry.ImgData{ 125 ID: id, 126 Tag: "", 127 }) 128 } 129 return imageIndex 130 } 131 132 type imagePushData struct { 133 id string 134 endpoint string 135 tokens []string 136 } 137 138 // lookupImageOnEndpoint checks the specified endpoint to see if an image exists 139 // and if it is absent then it sends the image id to the channel to be pushed. 140 func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *streamformatter.StreamFormatter, 141 images chan imagePushData, imagesToPush chan string) { 142 defer wg.Done() 143 for image := range images { 144 if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil { 145 logrus.Errorf("Error in LookupRemoteImage: %s", err) 146 imagesToPush <- image.id 147 continue 148 } 149 out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", stringid.TruncateID(image.id))) 150 } 151 } 152 153 func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteName string, imageIDs []string, 154 tags map[string][]string, repo *registry.RepositoryData, sf *streamformatter.StreamFormatter, r *registry.Session) error { 155 workerCount := len(imageIDs) 156 // start a maximum of 5 workers to check if images exist on the specified endpoint. 157 if workerCount > 5 { 158 workerCount = 5 159 } 160 var ( 161 wg = &sync.WaitGroup{} 162 imageData = make(chan imagePushData, workerCount*2) 163 imagesToPush = make(chan string, workerCount*2) 164 pushes = make(chan map[string]struct{}, 1) 165 ) 166 for i := 0; i < workerCount; i++ { 167 wg.Add(1) 168 go lookupImageOnEndpoint(wg, r, out, sf, imageData, imagesToPush) 169 } 170 // start a go routine that consumes the images to push 171 go func() { 172 shouldPush := make(map[string]struct{}) 173 for id := range imagesToPush { 174 shouldPush[id] = struct{}{} 175 } 176 pushes <- shouldPush 177 }() 178 for _, id := range imageIDs { 179 imageData <- imagePushData{ 180 id: id, 181 endpoint: endpoint, 182 tokens: repo.Tokens, 183 } 184 } 185 // close the channel to notify the workers that there will be no more images to check. 186 close(imageData) 187 wg.Wait() 188 close(imagesToPush) 189 // wait for all the images that require pushes to be collected into a consumable map. 190 shouldPush := <-pushes 191 // finish by pushing any images and tags to the endpoint. The order that the images are pushed 192 // is very important that is why we are still iterating over the ordered list of imageIDs. 193 for _, id := range imageIDs { 194 if _, push := shouldPush[id]; push { 195 if _, err := s.pushImage(r, out, id, endpoint, repo.Tokens, sf); err != nil { 196 // FIXME: Continue on error? 197 return err 198 } 199 } 200 for _, tag := range tags[id] { 201 out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag)) 202 if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil { 203 return err 204 } 205 } 206 } 207 return nil 208 } 209 210 // pushRepository pushes layers that do not already exist on the registry. 211 func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, 212 repoInfo *registry.RepositoryInfo, localRepo map[string]string, 213 tag string, sf *streamformatter.StreamFormatter) error { 214 logrus.Debugf("Local repo: %s", localRepo) 215 out = ioutils.NewWriteFlusher(out) 216 imgList, tags, err := s.getImageList(localRepo, tag) 217 if err != nil { 218 return err 219 } 220 out.Write(sf.FormatStatus("", "Sending image list")) 221 222 imageIndex := s.createImageIndex(imgList, tags) 223 logrus.Debugf("Preparing to push %s with the following images and tags", localRepo) 224 for _, data := range imageIndex { 225 logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag) 226 } 227 // Register all the images in a repository with the registry 228 // If an image is not in this list it will not be associated with the repository 229 repoData, err := r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, false, nil) 230 if err != nil { 231 return err 232 } 233 nTag := 1 234 if tag == "" { 235 nTag = len(localRepo) 236 } 237 out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", repoInfo.CanonicalName, nTag)) 238 // push the repository to each of the endpoints only if it does not exist. 239 for _, endpoint := range repoData.Endpoints { 240 if err := s.pushImageToEndpoint(endpoint, out, repoInfo.RemoteName, imgList, tags, repoData, sf, r); err != nil { 241 return err 242 } 243 } 244 _, err = r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, true, repoData.Endpoints) 245 return err 246 } 247 248 func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *streamformatter.StreamFormatter) (checksum string, err error) { 249 out = ioutils.NewWriteFlusher(out) 250 jsonRaw, err := ioutil.ReadFile(filepath.Join(s.graph.Root, imgID, "json")) 251 if err != nil { 252 return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err) 253 } 254 out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil)) 255 256 imgData := ®istry.ImgData{ 257 ID: imgID, 258 } 259 260 // Send the json 261 if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { 262 if err == registry.ErrAlreadyExists { 263 out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) 264 return "", nil 265 } 266 return "", err 267 } 268 269 layerData, err := s.graph.TempLayerArchive(imgID, sf, out) 270 if err != nil { 271 return "", fmt.Errorf("Failed to generate layer archive: %s", err) 272 } 273 defer os.RemoveAll(layerData.Name()) 274 275 // Send the layer 276 logrus.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size) 277 278 checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, 279 progressreader.New(progressreader.Config{ 280 In: layerData, 281 Out: out, 282 Formatter: sf, 283 Size: int(layerData.Size), 284 NewLines: false, 285 ID: stringid.TruncateID(imgData.ID), 286 Action: "Pushing", 287 }), ep, token, jsonRaw) 288 if err != nil { 289 return "", err 290 } 291 imgData.Checksum = checksum 292 imgData.ChecksumPayload = checksumPayload 293 // Send the checksum 294 if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { 295 return "", err 296 } 297 298 out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image successfully pushed", nil)) 299 return imgData.Checksum, nil 300 } 301 302 func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error { 303 endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) 304 if err != nil { 305 if repoInfo.Index.Official { 306 logrus.Debugf("Unable to push to V2 registry, falling back to v1: %s", err) 307 return ErrV2RegistryUnavailable 308 } 309 return fmt.Errorf("error getting registry endpoint: %s", err) 310 } 311 312 tags, err := s.getImageTags(localRepo, tag) 313 if err != nil { 314 return err 315 } 316 if len(tags) == 0 { 317 return fmt.Errorf("No tags to push for %s", repoInfo.LocalName) 318 } 319 320 auth, err := r.GetV2Authorization(endpoint, repoInfo.RemoteName, false) 321 if err != nil { 322 return fmt.Errorf("error getting authorization: %s", err) 323 } 324 325 for _, tag := range tags { 326 logrus.Debugf("Pushing repository: %s:%s", repoInfo.CanonicalName, tag) 327 328 layerId, exists := localRepo[tag] 329 if !exists { 330 return fmt.Errorf("tag does not exist: %s", tag) 331 } 332 333 layer, err := s.graph.Get(layerId) 334 if err != nil { 335 return err 336 } 337 338 m := ®istry.ManifestData{ 339 SchemaVersion: 1, 340 Name: repoInfo.RemoteName, 341 Tag: tag, 342 Architecture: layer.Architecture, 343 } 344 var metadata runconfig.Config 345 if layer.Config != nil { 346 metadata = *layer.Config 347 } 348 349 layersSeen := make(map[string]bool) 350 layers := []*image.Image{layer} 351 for ; layer != nil; layer, err = layer.GetParent() { 352 if err != nil { 353 return err 354 } 355 356 if layersSeen[layer.ID] { 357 break 358 } 359 layers = append(layers, layer) 360 layersSeen[layer.ID] = true 361 } 362 m.FSLayers = make([]*registry.FSLayer, len(layers)) 363 m.History = make([]*registry.ManifestHistory, len(layers)) 364 365 // Schema version 1 requires layer ordering from top to root 366 for i, layer := range layers { 367 logrus.Debugf("Pushing layer: %s", layer.ID) 368 369 if layer.Config != nil && metadata.Image != layer.ID { 370 if err := runconfig.Merge(&metadata, layer.Config); err != nil { 371 return err 372 } 373 } 374 jsonData, err := layer.RawJson() 375 if err != nil { 376 return fmt.Errorf("cannot retrieve the path for %s: %s", layer.ID, err) 377 } 378 379 checksum, err := layer.GetCheckSum(s.graph.ImageRoot(layer.ID)) 380 if err != nil { 381 return fmt.Errorf("error getting image checksum: %s", err) 382 } 383 384 var exists bool 385 if len(checksum) > 0 { 386 dgst, err := digest.ParseDigest(checksum) 387 if err != nil { 388 return fmt.Errorf("Invalid checksum %s: %s", checksum, err) 389 } 390 391 // Call mount blob 392 exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, dgst, auth) 393 if err != nil { 394 out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil)) 395 return err 396 } 397 } 398 if !exists { 399 if cs, err := s.pushV2Image(r, layer, endpoint, repoInfo.RemoteName, sf, out, auth); err != nil { 400 return err 401 } else if cs != checksum { 402 // Cache new checksum 403 if err := layer.SaveCheckSum(s.graph.ImageRoot(layer.ID), cs); err != nil { 404 return err 405 } 406 checksum = cs 407 } 408 } else { 409 out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image already exists", nil)) 410 } 411 m.FSLayers[i] = ®istry.FSLayer{BlobSum: checksum} 412 m.History[i] = ®istry.ManifestHistory{V1Compatibility: string(jsonData)} 413 } 414 415 if err := checkValidManifest(m); err != nil { 416 return fmt.Errorf("invalid manifest: %s", err) 417 } 418 419 logrus.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag) 420 mBytes, err := json.MarshalIndent(m, "", " ") 421 if err != nil { 422 return err 423 } 424 js, err := libtrust.NewJSONSignature(mBytes) 425 if err != nil { 426 return err 427 } 428 429 if err = js.Sign(s.trustKey); err != nil { 430 return err 431 } 432 433 signedBody, err := js.PrettySignature("signatures") 434 if err != nil { 435 return err 436 } 437 logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID()) 438 439 // push the manifest 440 digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, signedBody, mBytes, auth) 441 if err != nil { 442 return err 443 } 444 445 out.Write(sf.FormatStatus("", "Digest: %s", digest)) 446 } 447 return nil 448 } 449 450 // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk 451 func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *streamformatter.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) { 452 out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) 453 454 image, err := s.graph.Get(img.ID) 455 if err != nil { 456 return "", err 457 } 458 arch, err := image.TarLayer() 459 if err != nil { 460 return "", err 461 } 462 defer arch.Close() 463 464 tf, err := s.graph.newTempFile() 465 if err != nil { 466 return "", err 467 } 468 defer func() { 469 tf.Close() 470 os.Remove(tf.Name()) 471 }() 472 473 size, dgst, err := bufferToFile(tf, arch) 474 475 // Send the layer 476 logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) 477 478 if err := r.PutV2ImageBlob(endpoint, imageName, dgst, 479 progressreader.New(progressreader.Config{ 480 In: tf, 481 Out: out, 482 Formatter: sf, 483 Size: int(size), 484 NewLines: false, 485 ID: stringid.TruncateID(img.ID), 486 Action: "Pushing", 487 }), auth); err != nil { 488 out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image push failed", nil)) 489 return "", err 490 } 491 out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil)) 492 return dgst.String(), nil 493 } 494 495 // FIXME: Allow to interrupt current push when new push of same image is done. 496 func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error { 497 var ( 498 sf = streamformatter.NewJSONStreamFormatter() 499 ) 500 501 // Resolve the Repository name from fqn to RepositoryInfo 502 repoInfo, err := s.registryService.ResolveRepository(localName) 503 if err != nil { 504 return err 505 } 506 507 if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { 508 return err 509 } 510 defer s.poolRemove("push", repoInfo.LocalName) 511 512 endpoint, err := repoInfo.GetEndpoint() 513 if err != nil { 514 return err 515 } 516 517 r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false) 518 if err != nil { 519 return err 520 } 521 522 reposLen := 1 523 if imagePushConfig.Tag == "" { 524 reposLen = len(s.Repositories[repoInfo.LocalName]) 525 } 526 527 imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) 528 529 // If it fails, try to get the repository 530 localRepo, exists := s.Repositories[repoInfo.LocalName] 531 if !exists { 532 return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) 533 } 534 535 if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { 536 err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf) 537 if err == nil { 538 s.eventsService.Log("push", repoInfo.LocalName, "") 539 return nil 540 } 541 542 if err != ErrV2RegistryUnavailable { 543 return fmt.Errorf("Error pushing to registry: %s", err) 544 } 545 } 546 547 if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil { 548 return err 549 } 550 s.eventsService.Log("push", repoInfo.LocalName, "") 551 return nil 552 553 }