github.com/microsoft/docker@v1.5.0-rc2/graph/push.go (about) 1 package graph 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path" 10 "strings" 11 "sync" 12 13 log "github.com/Sirupsen/logrus" 14 "github.com/docker/docker/engine" 15 "github.com/docker/docker/image" 16 "github.com/docker/docker/registry" 17 "github.com/docker/docker/utils" 18 "github.com/docker/libtrust" 19 ) 20 21 // Retrieve the all the images to be uploaded in the correct order 22 func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { 23 var ( 24 imageList []string 25 imagesSeen = make(map[string]bool) 26 tagsByImage = make(map[string][]string) 27 ) 28 29 for tag, id := range localRepo { 30 if requestedTag != "" && requestedTag != tag { 31 continue 32 } 33 var imageListForThisTag []string 34 35 tagsByImage[id] = append(tagsByImage[id], tag) 36 37 for img, err := s.graph.Get(id); img != nil; img, err = img.GetParent() { 38 if err != nil { 39 return nil, nil, err 40 } 41 42 if imagesSeen[img.ID] { 43 // This image is already on the list, we can ignore it and all its parents 44 break 45 } 46 47 imagesSeen[img.ID] = true 48 imageListForThisTag = append(imageListForThisTag, img.ID) 49 } 50 51 // reverse the image list for this tag (so the "most"-parent image is first) 52 for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 { 53 imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] 54 } 55 56 // append to main image list 57 imageList = append(imageList, imageListForThisTag...) 58 } 59 if len(imageList) == 0 { 60 return nil, nil, fmt.Errorf("No images found for the requested repository / tag") 61 } 62 log.Debugf("Image list: %v", imageList) 63 log.Debugf("Tags by image: %v", tagsByImage) 64 65 return imageList, tagsByImage, nil 66 } 67 68 // createImageIndex returns an index of an image's layer IDs and tags. 69 func (s *TagStore) createImageIndex(images []string, tags map[string][]string) []*registry.ImgData { 70 var imageIndex []*registry.ImgData 71 for _, id := range images { 72 if tags, hasTags := tags[id]; hasTags { 73 // If an image has tags you must add an entry in the image index 74 // for each tag 75 for _, tag := range tags { 76 imageIndex = append(imageIndex, ®istry.ImgData{ 77 ID: id, 78 Tag: tag, 79 }) 80 } 81 continue 82 } 83 // If the image does not have a tag it still needs to be sent to the 84 // registry with an empty tag so that it is accociated with the repository 85 imageIndex = append(imageIndex, ®istry.ImgData{ 86 ID: id, 87 Tag: "", 88 }) 89 } 90 return imageIndex 91 } 92 93 type imagePushData struct { 94 id string 95 endpoint string 96 tokens []string 97 } 98 99 // lookupImageOnEndpoint checks the specified endpoint to see if an image exists 100 // and if it is absent then it sends the image id to the channel to be pushed. 101 func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *utils.StreamFormatter, 102 images chan imagePushData, imagesToPush chan string) { 103 defer wg.Done() 104 for image := range images { 105 if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil { 106 log.Errorf("Error in LookupRemoteImage: %s", err) 107 imagesToPush <- image.id 108 continue 109 } 110 out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(image.id))) 111 } 112 } 113 114 func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteName string, imageIDs []string, 115 tags map[string][]string, repo *registry.RepositoryData, sf *utils.StreamFormatter, r *registry.Session) error { 116 workerCount := len(imageIDs) 117 // start a maximum of 5 workers to check if images exist on the specified endpoint. 118 if workerCount > 5 { 119 workerCount = 5 120 } 121 var ( 122 wg = &sync.WaitGroup{} 123 imageData = make(chan imagePushData, workerCount*2) 124 imagesToPush = make(chan string, workerCount*2) 125 pushes = make(chan map[string]struct{}, 1) 126 ) 127 for i := 0; i < workerCount; i++ { 128 wg.Add(1) 129 go lookupImageOnEndpoint(wg, r, out, sf, imageData, imagesToPush) 130 } 131 // start a go routine that consumes the images to push 132 go func() { 133 shouldPush := make(map[string]struct{}) 134 for id := range imagesToPush { 135 shouldPush[id] = struct{}{} 136 } 137 pushes <- shouldPush 138 }() 139 for _, id := range imageIDs { 140 imageData <- imagePushData{ 141 id: id, 142 endpoint: endpoint, 143 tokens: repo.Tokens, 144 } 145 } 146 // close the channel to notify the workers that there will be no more images to check. 147 close(imageData) 148 wg.Wait() 149 close(imagesToPush) 150 // wait for all the images that require pushes to be collected into a consumable map. 151 shouldPush := <-pushes 152 // finish by pushing any images and tags to the endpoint. The order that the images are pushed 153 // is very important that is why we are still itterating over the ordered list of imageIDs. 154 for _, id := range imageIDs { 155 if _, push := shouldPush[id]; push { 156 if _, err := s.pushImage(r, out, id, endpoint, repo.Tokens, sf); err != nil { 157 // FIXME: Continue on error? 158 return err 159 } 160 } 161 for _, tag := range tags[id] { 162 out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag)) 163 if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil { 164 return err 165 } 166 } 167 } 168 return nil 169 } 170 171 // pushRepository pushes layers that do not already exist on the registry. 172 func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, 173 repoInfo *registry.RepositoryInfo, localRepo map[string]string, 174 tag string, sf *utils.StreamFormatter) error { 175 log.Debugf("Local repo: %s", localRepo) 176 out = utils.NewWriteFlusher(out) 177 imgList, tags, err := s.getImageList(localRepo, tag) 178 if err != nil { 179 return err 180 } 181 out.Write(sf.FormatStatus("", "Sending image list")) 182 183 imageIndex := s.createImageIndex(imgList, tags) 184 log.Debugf("Preparing to push %s with the following images and tags", localRepo) 185 for _, data := range imageIndex { 186 log.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag) 187 } 188 // Register all the images in a repository with the registry 189 // If an image is not in this list it will not be associated with the repository 190 repoData, err := r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, false, nil) 191 if err != nil { 192 return err 193 } 194 nTag := 1 195 if tag == "" { 196 nTag = len(localRepo) 197 } 198 out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", repoInfo.CanonicalName, nTag)) 199 // push the repository to each of the endpoints only if it does not exist. 200 for _, endpoint := range repoData.Endpoints { 201 if err := s.pushImageToEndpoint(endpoint, out, repoInfo.RemoteName, imgList, tags, repoData, sf, r); err != nil { 202 return err 203 } 204 } 205 _, err = r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, true, repoData.Endpoints) 206 return err 207 } 208 209 func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { 210 out = utils.NewWriteFlusher(out) 211 jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) 212 if err != nil { 213 return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err) 214 } 215 out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil)) 216 217 imgData := ®istry.ImgData{ 218 ID: imgID, 219 } 220 221 // Send the json 222 if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { 223 if err == registry.ErrAlreadyExists { 224 out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) 225 return "", nil 226 } 227 return "", err 228 } 229 230 layerData, err := s.graph.TempLayerArchive(imgID, sf, out) 231 if err != nil { 232 return "", fmt.Errorf("Failed to generate layer archive: %s", err) 233 } 234 defer os.RemoveAll(layerData.Name()) 235 236 // Send the layer 237 log.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size) 238 239 checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw) 240 if err != nil { 241 return "", err 242 } 243 imgData.Checksum = checksum 244 imgData.ChecksumPayload = checksumPayload 245 // Send the checksum 246 if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil { 247 return "", err 248 } 249 250 out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil)) 251 return imgData.Checksum, nil 252 } 253 254 func (s *TagStore) pushV2Repository(r *registry.Session, eng *engine.Engine, out io.Writer, repoInfo *registry.RepositoryInfo, manifestBytes, tag string, sf *utils.StreamFormatter) error { 255 if repoInfo.Official { 256 j := eng.Job("trust_update_base") 257 if err := j.Run(); err != nil { 258 log.Errorf("error updating trust base graph: %s", err) 259 } 260 } 261 262 endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) 263 if err != nil { 264 return fmt.Errorf("error getting registry endpoint: %s", err) 265 } 266 auth, err := r.GetV2Authorization(endpoint, repoInfo.RemoteName, false) 267 if err != nil { 268 return fmt.Errorf("error getting authorization: %s", err) 269 } 270 271 // if no manifest is given, generate and sign with the key associated with the local tag store 272 if len(manifestBytes) == 0 { 273 mBytes, err := s.newManifest(repoInfo.LocalName, repoInfo.RemoteName, tag) 274 if err != nil { 275 return err 276 } 277 js, err := libtrust.NewJSONSignature(mBytes) 278 if err != nil { 279 return err 280 } 281 282 if err = js.Sign(s.trustKey); err != nil { 283 return err 284 } 285 286 signedBody, err := js.PrettySignature("signatures") 287 if err != nil { 288 return err 289 } 290 log.Infof("Signed manifest using daemon's key: %s", s.trustKey.KeyID()) 291 292 manifestBytes = string(signedBody) 293 } 294 295 manifest, verified, err := s.verifyManifest(eng, []byte(manifestBytes)) 296 if err != nil { 297 return fmt.Errorf("error verifying manifest: %s", err) 298 } 299 300 if err := checkValidManifest(manifest); err != nil { 301 return fmt.Errorf("invalid manifest: %s", err) 302 } 303 304 if !verified { 305 log.Debugf("Pushing unverified image") 306 } 307 308 for i := len(manifest.FSLayers) - 1; i >= 0; i-- { 309 var ( 310 sumStr = manifest.FSLayers[i].BlobSum 311 imgJSON = []byte(manifest.History[i].V1Compatibility) 312 ) 313 314 sumParts := strings.SplitN(sumStr, ":", 2) 315 if len(sumParts) < 2 { 316 return fmt.Errorf("Invalid checksum: %s", sumStr) 317 } 318 manifestSum := sumParts[1] 319 320 img, err := image.NewImgJSON(imgJSON) 321 if err != nil { 322 return fmt.Errorf("Failed to parse json: %s", err) 323 } 324 325 // Call mount blob 326 exists, err := r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, auth) 327 if err != nil { 328 out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image push failed", nil)) 329 return err 330 } 331 332 if !exists { 333 if err := s.PushV2Image(r, img, endpoint, repoInfo.RemoteName, sumParts[0], manifestSum, sf, out, auth); err != nil { 334 return err 335 } 336 } else { 337 out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image already exists", nil)) 338 } 339 } 340 341 // push the manifest 342 return r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, bytes.NewReader([]byte(manifestBytes)), auth) 343 } 344 345 // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk 346 func (s *TagStore) PushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName, sumType, sumStr string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) error { 347 out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Buffering to Disk", nil)) 348 349 image, err := s.graph.Get(img.ID) 350 if err != nil { 351 return err 352 } 353 arch, err := image.TarLayer() 354 if err != nil { 355 return err 356 } 357 tf, err := s.graph.newTempFile() 358 if err != nil { 359 return err 360 } 361 defer func() { 362 tf.Close() 363 os.Remove(tf.Name()) 364 }() 365 366 size, err := bufferToFile(tf, arch) 367 if err != nil { 368 return err 369 } 370 371 // Send the layer 372 log.Debugf("rendered layer for %s of [%d] size", img.ID, size) 373 374 if err := r.PutV2ImageBlob(endpoint, imageName, sumType, sumStr, utils.ProgressReader(tf, int(size), out, sf, false, utils.TruncateID(img.ID), "Pushing"), auth); err != nil { 375 out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image push failed", nil)) 376 return err 377 } 378 out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Image successfully pushed", nil)) 379 return nil 380 } 381 382 // FIXME: Allow to interrupt current push when new push of same image is done. 383 func (s *TagStore) CmdPush(job *engine.Job) engine.Status { 384 if n := len(job.Args); n != 1 { 385 return job.Errorf("Usage: %s IMAGE", job.Name) 386 } 387 var ( 388 localName = job.Args[0] 389 sf = utils.NewStreamFormatter(job.GetenvBool("json")) 390 authConfig = ®istry.AuthConfig{} 391 metaHeaders map[string][]string 392 ) 393 394 // Resolve the Repository name from fqn to RepositoryInfo 395 repoInfo, err := registry.ResolveRepositoryInfo(job, localName) 396 if err != nil { 397 return job.Error(err) 398 } 399 400 tag := job.Getenv("tag") 401 manifestBytes := job.Getenv("manifest") 402 job.GetenvJson("authConfig", authConfig) 403 job.GetenvJson("metaHeaders", &metaHeaders) 404 405 if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { 406 return job.Error(err) 407 } 408 defer s.poolRemove("push", repoInfo.LocalName) 409 410 endpoint, err := repoInfo.GetEndpoint() 411 if err != nil { 412 return job.Error(err) 413 } 414 415 img, err := s.graph.Get(repoInfo.LocalName) 416 r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) 417 if err2 != nil { 418 return job.Error(err2) 419 } 420 421 if len(tag) == 0 { 422 tag = DEFAULTTAG 423 } 424 425 if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { 426 err := s.pushV2Repository(r, job.Eng, job.Stdout, repoInfo, manifestBytes, tag, sf) 427 if err == nil { 428 return engine.StatusOK 429 } 430 431 // error out, no fallback to V1 432 return job.Errorf("Error pushing to registry: %s", err) 433 } 434 435 if err != nil { 436 reposLen := 1 437 if tag == "" { 438 reposLen = len(s.Repositories[repoInfo.LocalName]) 439 } 440 job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) 441 // If it fails, try to get the repository 442 if localRepo, exists := s.Repositories[repoInfo.LocalName]; exists { 443 if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { 444 return job.Error(err) 445 } 446 return engine.StatusOK 447 } 448 return job.Error(err) 449 } 450 451 var token []string 452 job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", repoInfo.CanonicalName)) 453 if _, err := s.pushImage(r, job.Stdout, img.ID, endpoint.String(), token, sf); err != nil { 454 return job.Error(err) 455 } 456 return engine.StatusOK 457 }