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