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