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, &registry.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, &registry.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 := &registry.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  = &registry.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  }