github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/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, &registry.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, &registry.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 := &registry.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 := &registry.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] = &registry.FSLayer{BlobSum: checksum}
   412  			m.History[i] = &registry.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  }