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