github.com/crquan/docker@v1.8.1/graph/push_v1.go (about)

     1  package graph
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"sync"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/distribution/registry/client/transport"
    11  	"github.com/docker/docker/pkg/ioutils"
    12  	"github.com/docker/docker/pkg/progressreader"
    13  	"github.com/docker/docker/pkg/streamformatter"
    14  	"github.com/docker/docker/pkg/stringid"
    15  	"github.com/docker/docker/registry"
    16  	"github.com/docker/docker/utils"
    17  )
    18  
    19  type v1Pusher struct {
    20  	*TagStore
    21  	endpoint  registry.APIEndpoint
    22  	localRepo Repository
    23  	repoInfo  *registry.RepositoryInfo
    24  	config    *ImagePushConfig
    25  	sf        *streamformatter.StreamFormatter
    26  	session   *registry.Session
    27  
    28  	out io.Writer
    29  }
    30  
    31  func (p *v1Pusher) Push() (fallback bool, err error) {
    32  	tlsConfig, err := p.registryService.TLSConfig(p.repoInfo.Index.Name)
    33  	if err != nil {
    34  		return false, err
    35  	}
    36  	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
    37  	tr := transport.NewTransport(
    38  		// TODO(tiborvass): was NoTimeout
    39  		registry.NewTransport(tlsConfig),
    40  		registry.DockerHeaders(p.config.MetaHeaders)...,
    41  	)
    42  	client := registry.HTTPClient(tr)
    43  	v1Endpoint, err := p.endpoint.ToV1Endpoint(p.config.MetaHeaders)
    44  	if err != nil {
    45  		logrus.Debugf("Could not get v1 endpoint: %v", err)
    46  		return true, err
    47  	}
    48  	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
    49  	if err != nil {
    50  		// TODO(dmcgowan): Check if should fallback
    51  		return true, err
    52  	}
    53  	if err := p.pushRepository(p.config.Tag); err != nil {
    54  		// TODO(dmcgowan): Check if should fallback
    55  		return false, err
    56  	}
    57  	return false, nil
    58  }
    59  
    60  // Retrieve the all the images to be uploaded in the correct order
    61  func (p *v1Pusher) getImageList(requestedTag string) ([]string, map[string][]string, error) {
    62  	var (
    63  		imageList   []string
    64  		imagesSeen  = make(map[string]bool)
    65  		tagsByImage = make(map[string][]string)
    66  	)
    67  
    68  	for tag, id := range p.localRepo {
    69  		if requestedTag != "" && requestedTag != tag {
    70  			// Include only the requested tag.
    71  			continue
    72  		}
    73  
    74  		if utils.DigestReference(tag) {
    75  			// Ignore digest references.
    76  			continue
    77  		}
    78  
    79  		var imageListForThisTag []string
    80  
    81  		tagsByImage[id] = append(tagsByImage[id], tag)
    82  
    83  		for img, err := p.graph.Get(id); img != nil; img, err = p.graph.GetParent(img) {
    84  			if err != nil {
    85  				return nil, nil, err
    86  			}
    87  
    88  			if imagesSeen[img.ID] {
    89  				// This image is already on the list, we can ignore it and all its parents
    90  				break
    91  			}
    92  
    93  			imagesSeen[img.ID] = true
    94  			imageListForThisTag = append(imageListForThisTag, img.ID)
    95  		}
    96  
    97  		// reverse the image list for this tag (so the "most"-parent image is first)
    98  		for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
    99  			imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i]
   100  		}
   101  
   102  		// append to main image list
   103  		imageList = append(imageList, imageListForThisTag...)
   104  	}
   105  	if len(imageList) == 0 {
   106  		return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
   107  	}
   108  	logrus.Debugf("Image list: %v", imageList)
   109  	logrus.Debugf("Tags by image: %v", tagsByImage)
   110  
   111  	return imageList, tagsByImage, nil
   112  }
   113  
   114  // createImageIndex returns an index of an image's layer IDs and tags.
   115  func (s *TagStore) createImageIndex(images []string, tags map[string][]string) []*registry.ImgData {
   116  	var imageIndex []*registry.ImgData
   117  	for _, id := range images {
   118  		if tags, hasTags := tags[id]; hasTags {
   119  			// If an image has tags you must add an entry in the image index
   120  			// for each tag
   121  			for _, tag := range tags {
   122  				imageIndex = append(imageIndex, &registry.ImgData{
   123  					ID:  id,
   124  					Tag: tag,
   125  				})
   126  			}
   127  			continue
   128  		}
   129  		// If the image does not have a tag it still needs to be sent to the
   130  		// registry with an empty tag so that it is accociated with the repository
   131  		imageIndex = append(imageIndex, &registry.ImgData{
   132  			ID:  id,
   133  			Tag: "",
   134  		})
   135  	}
   136  	return imageIndex
   137  }
   138  
   139  type imagePushData struct {
   140  	id       string
   141  	endpoint string
   142  	tokens   []string
   143  }
   144  
   145  // lookupImageOnEndpoint checks the specified endpoint to see if an image exists
   146  // and if it is absent then it sends the image id to the channel to be pushed.
   147  func (p *v1Pusher) lookupImageOnEndpoint(wg *sync.WaitGroup, images chan imagePushData, imagesToPush chan string) {
   148  	defer wg.Done()
   149  	for image := range images {
   150  		if err := p.session.LookupRemoteImage(image.id, image.endpoint); err != nil {
   151  			logrus.Errorf("Error in LookupRemoteImage: %s", err)
   152  			imagesToPush <- image.id
   153  			continue
   154  		}
   155  		p.out.Write(p.sf.FormatStatus("", "Image %s already pushed, skipping", stringid.TruncateID(image.id)))
   156  	}
   157  }
   158  
   159  func (p *v1Pusher) pushImageToEndpoint(endpoint string, imageIDs []string, tags map[string][]string, repo *registry.RepositoryData) error {
   160  	workerCount := len(imageIDs)
   161  	// start a maximum of 5 workers to check if images exist on the specified endpoint.
   162  	if workerCount > 5 {
   163  		workerCount = 5
   164  	}
   165  	var (
   166  		wg           = &sync.WaitGroup{}
   167  		imageData    = make(chan imagePushData, workerCount*2)
   168  		imagesToPush = make(chan string, workerCount*2)
   169  		pushes       = make(chan map[string]struct{}, 1)
   170  	)
   171  	for i := 0; i < workerCount; i++ {
   172  		wg.Add(1)
   173  		go p.lookupImageOnEndpoint(wg, imageData, imagesToPush)
   174  	}
   175  	// start a go routine that consumes the images to push
   176  	go func() {
   177  		shouldPush := make(map[string]struct{})
   178  		for id := range imagesToPush {
   179  			shouldPush[id] = struct{}{}
   180  		}
   181  		pushes <- shouldPush
   182  	}()
   183  	for _, id := range imageIDs {
   184  		imageData <- imagePushData{
   185  			id:       id,
   186  			endpoint: endpoint,
   187  			tokens:   repo.Tokens,
   188  		}
   189  	}
   190  	// close the channel to notify the workers that there will be no more images to check.
   191  	close(imageData)
   192  	wg.Wait()
   193  	close(imagesToPush)
   194  	// wait for all the images that require pushes to be collected into a consumable map.
   195  	shouldPush := <-pushes
   196  	// finish by pushing any images and tags to the endpoint.  The order that the images are pushed
   197  	// is very important that is why we are still iterating over the ordered list of imageIDs.
   198  	for _, id := range imageIDs {
   199  		if _, push := shouldPush[id]; push {
   200  			if _, err := p.pushImage(id, endpoint, repo.Tokens); err != nil {
   201  				// FIXME: Continue on error?
   202  				return err
   203  			}
   204  		}
   205  		for _, tag := range tags[id] {
   206  			p.out.Write(p.sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+p.repoInfo.RemoteName+"/tags/"+tag))
   207  			if err := p.session.PushRegistryTag(p.repoInfo.RemoteName, id, tag, endpoint); err != nil {
   208  				return err
   209  			}
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  // pushRepository pushes layers that do not already exist on the registry.
   216  func (p *v1Pusher) pushRepository(tag string) error {
   217  
   218  	logrus.Debugf("Local repo: %s", p.localRepo)
   219  	p.out = ioutils.NewWriteFlusher(p.config.OutStream)
   220  	imgList, tags, err := p.getImageList(tag)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	p.out.Write(p.sf.FormatStatus("", "Sending image list"))
   225  
   226  	imageIndex := p.createImageIndex(imgList, tags)
   227  	logrus.Debugf("Preparing to push %s with the following images and tags", p.localRepo)
   228  	for _, data := range imageIndex {
   229  		logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
   230  	}
   231  
   232  	if _, err := p.poolAdd("push", p.repoInfo.LocalName); err != nil {
   233  		return err
   234  	}
   235  	defer p.poolRemove("push", p.repoInfo.LocalName)
   236  
   237  	// Register all the images in a repository with the registry
   238  	// If an image is not in this list it will not be associated with the repository
   239  	repoData, err := p.session.PushImageJSONIndex(p.repoInfo.RemoteName, imageIndex, false, nil)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	nTag := 1
   244  	if tag == "" {
   245  		nTag = len(p.localRepo)
   246  	}
   247  	p.out.Write(p.sf.FormatStatus("", "Pushing repository %s (%d tags)", p.repoInfo.CanonicalName, nTag))
   248  	// push the repository to each of the endpoints only if it does not exist.
   249  	for _, endpoint := range repoData.Endpoints {
   250  		if err := p.pushImageToEndpoint(endpoint, imgList, tags, repoData); err != nil {
   251  			return err
   252  		}
   253  	}
   254  	_, err = p.session.PushImageJSONIndex(p.repoInfo.RemoteName, imageIndex, true, repoData.Endpoints)
   255  	return err
   256  }
   257  
   258  func (p *v1Pusher) pushImage(imgID, ep string, token []string) (checksum string, err error) {
   259  	jsonRaw, err := p.graph.RawJSON(imgID)
   260  	if err != nil {
   261  		return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
   262  	}
   263  	p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil))
   264  
   265  	imgData := &registry.ImgData{
   266  		ID: imgID,
   267  	}
   268  
   269  	// Send the json
   270  	if err := p.session.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
   271  		if err == registry.ErrAlreadyExists {
   272  			p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
   273  			return "", nil
   274  		}
   275  		return "", err
   276  	}
   277  
   278  	layerData, err := p.graph.TempLayerArchive(imgID, p.sf, p.out)
   279  	if err != nil {
   280  		return "", fmt.Errorf("Failed to generate layer archive: %s", err)
   281  	}
   282  	defer os.RemoveAll(layerData.Name())
   283  
   284  	// Send the layer
   285  	logrus.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
   286  
   287  	checksum, checksumPayload, err := p.session.PushImageLayerRegistry(imgData.ID,
   288  		progressreader.New(progressreader.Config{
   289  			In:        layerData,
   290  			Out:       p.out,
   291  			Formatter: p.sf,
   292  			Size:      int(layerData.Size),
   293  			NewLines:  false,
   294  			ID:        stringid.TruncateID(imgData.ID),
   295  			Action:    "Pushing",
   296  		}), ep, jsonRaw)
   297  	if err != nil {
   298  		return "", err
   299  	}
   300  	imgData.Checksum = checksum
   301  	imgData.ChecksumPayload = checksumPayload
   302  	// Send the checksum
   303  	if err := p.session.PushImageChecksumRegistry(imgData, ep); err != nil {
   304  		return "", err
   305  	}
   306  
   307  	p.out.Write(p.sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image successfully pushed", nil))
   308  	return imgData.Checksum, nil
   309  }