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, &registry.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, &registry.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 := &registry.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 := &registry.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] = &registry.FSLayer{BlobSum: dgst.String()}
   409  			m.History[i] = &registry.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  }