github.com/OpenFlowLabs/moby@v17.12.1-ce-rc2+incompatible/distribution/push_v1.go (about)

     1  package distribution
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/docker/distribution/reference"
     8  	"github.com/docker/distribution/registry/client/transport"
     9  	"github.com/docker/docker/distribution/metadata"
    10  	"github.com/docker/docker/dockerversion"
    11  	"github.com/docker/docker/image"
    12  	"github.com/docker/docker/image/v1"
    13  	"github.com/docker/docker/layer"
    14  	"github.com/docker/docker/pkg/ioutils"
    15  	"github.com/docker/docker/pkg/progress"
    16  	"github.com/docker/docker/pkg/stringid"
    17  	"github.com/docker/docker/registry"
    18  	"github.com/opencontainers/go-digest"
    19  	"github.com/sirupsen/logrus"
    20  	"golang.org/x/net/context"
    21  )
    22  
    23  type v1Pusher struct {
    24  	v1IDService *metadata.V1IDService
    25  	endpoint    registry.APIEndpoint
    26  	ref         reference.Named
    27  	repoInfo    *registry.RepositoryInfo
    28  	config      *ImagePushConfig
    29  	session     *registry.Session
    30  }
    31  
    32  func (p *v1Pusher) Push(ctx context.Context) error {
    33  	tlsConfig, err := p.config.RegistryService.TLSConfig(p.repoInfo.Index.Name)
    34  	if err != nil {
    35  		return err
    36  	}
    37  	// Adds Docker-specific headers as well as user-specified headers (metaHeaders)
    38  	tr := transport.NewTransport(
    39  		// TODO(tiborvass): was NoTimeout
    40  		registry.NewTransport(tlsConfig),
    41  		registry.Headers(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)...,
    42  	)
    43  	client := registry.HTTPClient(tr)
    44  	v1Endpoint := p.endpoint.ToV1Endpoint(dockerversion.DockerUserAgent(ctx), p.config.MetaHeaders)
    45  	p.session, err = registry.NewSession(client, p.config.AuthConfig, v1Endpoint)
    46  	if err != nil {
    47  		// TODO(dmcgowan): Check if should fallback
    48  		return fallbackError{err: err}
    49  	}
    50  	if err := p.pushRepository(ctx); err != nil {
    51  		// TODO(dmcgowan): Check if should fallback
    52  		return err
    53  	}
    54  	return nil
    55  }
    56  
    57  // v1Image exposes the configuration, filesystem layer ID, and a v1 ID for an
    58  // image being pushed to a v1 registry.
    59  type v1Image interface {
    60  	Config() []byte
    61  	Layer() layer.Layer
    62  	V1ID() string
    63  }
    64  
    65  type v1ImageCommon struct {
    66  	layer  layer.Layer
    67  	config []byte
    68  	v1ID   string
    69  }
    70  
    71  func (common *v1ImageCommon) Config() []byte {
    72  	return common.config
    73  }
    74  
    75  func (common *v1ImageCommon) V1ID() string {
    76  	return common.v1ID
    77  }
    78  
    79  func (common *v1ImageCommon) Layer() layer.Layer {
    80  	return common.layer
    81  }
    82  
    83  // v1TopImage defines a runnable (top layer) image being pushed to a v1
    84  // registry.
    85  type v1TopImage struct {
    86  	v1ImageCommon
    87  	imageID image.ID
    88  }
    89  
    90  func newV1TopImage(imageID image.ID, img *image.Image, l layer.Layer, parent *v1DependencyImage) (*v1TopImage, error) {
    91  	v1ID := imageID.Digest().Hex()
    92  	parentV1ID := ""
    93  	if parent != nil {
    94  		parentV1ID = parent.V1ID()
    95  	}
    96  
    97  	config, err := v1.MakeV1ConfigFromConfig(img, v1ID, parentV1ID, false)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return &v1TopImage{
   103  		v1ImageCommon: v1ImageCommon{
   104  			v1ID:   v1ID,
   105  			config: config,
   106  			layer:  l,
   107  		},
   108  		imageID: imageID,
   109  	}, nil
   110  }
   111  
   112  // v1DependencyImage defines a dependency layer being pushed to a v1 registry.
   113  type v1DependencyImage struct {
   114  	v1ImageCommon
   115  }
   116  
   117  func newV1DependencyImage(l layer.Layer, parent *v1DependencyImage) *v1DependencyImage {
   118  	v1ID := digest.Digest(l.ChainID()).Hex()
   119  
   120  	var config string
   121  	if parent != nil {
   122  		config = fmt.Sprintf(`{"id":"%s","parent":"%s"}`, v1ID, parent.V1ID())
   123  	} else {
   124  		config = fmt.Sprintf(`{"id":"%s"}`, v1ID)
   125  	}
   126  	return &v1DependencyImage{
   127  		v1ImageCommon: v1ImageCommon{
   128  			v1ID:   v1ID,
   129  			config: []byte(config),
   130  			layer:  l,
   131  		},
   132  	}
   133  }
   134  
   135  // Retrieve the all the images to be uploaded in the correct order
   136  func (p *v1Pusher) getImageList() (imageList []v1Image, tagsByImage map[image.ID][]string, referencedLayers []PushLayer, err error) {
   137  	tagsByImage = make(map[image.ID][]string)
   138  
   139  	// Ignore digest references
   140  	if _, isCanonical := p.ref.(reference.Canonical); isCanonical {
   141  		return
   142  	}
   143  
   144  	tagged, isTagged := p.ref.(reference.NamedTagged)
   145  	if isTagged {
   146  		// Push a specific tag
   147  		var imgID image.ID
   148  		var dgst digest.Digest
   149  		dgst, err = p.config.ReferenceStore.Get(p.ref)
   150  		if err != nil {
   151  			return
   152  		}
   153  		imgID = image.IDFromDigest(dgst)
   154  
   155  		imageList, err = p.imageListForTag(imgID, nil, &referencedLayers)
   156  		if err != nil {
   157  			return
   158  		}
   159  
   160  		tagsByImage[imgID] = []string{tagged.Tag()}
   161  
   162  		return
   163  	}
   164  
   165  	imagesSeen := make(map[digest.Digest]struct{})
   166  	dependenciesSeen := make(map[layer.ChainID]*v1DependencyImage)
   167  
   168  	associations := p.config.ReferenceStore.ReferencesByName(p.ref)
   169  	for _, association := range associations {
   170  		if tagged, isTagged = association.Ref.(reference.NamedTagged); !isTagged {
   171  			// Ignore digest references.
   172  			continue
   173  		}
   174  
   175  		imgID := image.IDFromDigest(association.ID)
   176  		tagsByImage[imgID] = append(tagsByImage[imgID], tagged.Tag())
   177  
   178  		if _, present := imagesSeen[association.ID]; present {
   179  			// Skip generating image list for already-seen image
   180  			continue
   181  		}
   182  		imagesSeen[association.ID] = struct{}{}
   183  
   184  		imageListForThisTag, err := p.imageListForTag(imgID, dependenciesSeen, &referencedLayers)
   185  		if err != nil {
   186  			return nil, nil, nil, err
   187  		}
   188  
   189  		// append to main image list
   190  		imageList = append(imageList, imageListForThisTag...)
   191  	}
   192  	if len(imageList) == 0 {
   193  		return nil, nil, nil, fmt.Errorf("No images found for the requested repository / tag")
   194  	}
   195  	logrus.Debugf("Image list: %v", imageList)
   196  	logrus.Debugf("Tags by image: %v", tagsByImage)
   197  
   198  	return
   199  }
   200  
   201  func (p *v1Pusher) imageListForTag(imgID image.ID, dependenciesSeen map[layer.ChainID]*v1DependencyImage, referencedLayers *[]PushLayer) (imageListForThisTag []v1Image, err error) {
   202  	ics, ok := p.config.ImageStore.(*imageConfigStore)
   203  	if !ok {
   204  		return nil, fmt.Errorf("only image store images supported for v1 push")
   205  	}
   206  	img, err := ics.Store.Get(imgID)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	topLayerID := img.RootFS.ChainID()
   212  
   213  	pl, err := p.config.LayerStore.Get(topLayerID)
   214  	*referencedLayers = append(*referencedLayers, pl)
   215  	if err != nil {
   216  		return nil, fmt.Errorf("failed to get top layer from image: %v", err)
   217  	}
   218  
   219  	// V1 push is deprecated, only support existing layerstore layers
   220  	lsl, ok := pl.(*storeLayer)
   221  	if !ok {
   222  		return nil, fmt.Errorf("only layer store layers supported for v1 push")
   223  	}
   224  	l := lsl.Layer
   225  
   226  	dependencyImages, parent := generateDependencyImages(l.Parent(), dependenciesSeen)
   227  
   228  	topImage, err := newV1TopImage(imgID, img, l, parent)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	imageListForThisTag = append(dependencyImages, topImage)
   234  
   235  	return
   236  }
   237  
   238  func generateDependencyImages(l layer.Layer, dependenciesSeen map[layer.ChainID]*v1DependencyImage) (imageListForThisTag []v1Image, parent *v1DependencyImage) {
   239  	if l == nil {
   240  		return nil, nil
   241  	}
   242  
   243  	imageListForThisTag, parent = generateDependencyImages(l.Parent(), dependenciesSeen)
   244  
   245  	if dependenciesSeen != nil {
   246  		if dependencyImage, present := dependenciesSeen[l.ChainID()]; present {
   247  			// This layer is already on the list, we can ignore it
   248  			// and all its parents.
   249  			return imageListForThisTag, dependencyImage
   250  		}
   251  	}
   252  
   253  	dependencyImage := newV1DependencyImage(l, parent)
   254  	imageListForThisTag = append(imageListForThisTag, dependencyImage)
   255  
   256  	if dependenciesSeen != nil {
   257  		dependenciesSeen[l.ChainID()] = dependencyImage
   258  	}
   259  
   260  	return imageListForThisTag, dependencyImage
   261  }
   262  
   263  // createImageIndex returns an index of an image's layer IDs and tags.
   264  func createImageIndex(images []v1Image, tags map[image.ID][]string) []*registry.ImgData {
   265  	var imageIndex []*registry.ImgData
   266  	for _, img := range images {
   267  		v1ID := img.V1ID()
   268  
   269  		if topImage, isTopImage := img.(*v1TopImage); isTopImage {
   270  			if tags, hasTags := tags[topImage.imageID]; hasTags {
   271  				// If an image has tags you must add an entry in the image index
   272  				// for each tag
   273  				for _, tag := range tags {
   274  					imageIndex = append(imageIndex, &registry.ImgData{
   275  						ID:  v1ID,
   276  						Tag: tag,
   277  					})
   278  				}
   279  				continue
   280  			}
   281  		}
   282  
   283  		// If the image does not have a tag it still needs to be sent to the
   284  		// registry with an empty tag so that it is associated with the repository
   285  		imageIndex = append(imageIndex, &registry.ImgData{
   286  			ID:  v1ID,
   287  			Tag: "",
   288  		})
   289  	}
   290  	return imageIndex
   291  }
   292  
   293  // lookupImageOnEndpoint checks the specified endpoint to see if an image exists
   294  // and if it is absent then it sends the image id to the channel to be pushed.
   295  func (p *v1Pusher) lookupImageOnEndpoint(wg *sync.WaitGroup, endpoint string, images chan v1Image, imagesToPush chan string) {
   296  	defer wg.Done()
   297  	for image := range images {
   298  		v1ID := image.V1ID()
   299  		truncID := stringid.TruncateID(image.Layer().DiffID().String())
   300  		if err := p.session.LookupRemoteImage(v1ID, endpoint); err != nil {
   301  			logrus.Errorf("Error in LookupRemoteImage: %s", err)
   302  			imagesToPush <- v1ID
   303  			progress.Update(p.config.ProgressOutput, truncID, "Waiting")
   304  		} else {
   305  			progress.Update(p.config.ProgressOutput, truncID, "Already exists")
   306  		}
   307  	}
   308  }
   309  
   310  func (p *v1Pusher) pushImageToEndpoint(ctx context.Context, endpoint string, imageList []v1Image, tags map[image.ID][]string, repo *registry.RepositoryData) error {
   311  	workerCount := len(imageList)
   312  	// start a maximum of 5 workers to check if images exist on the specified endpoint.
   313  	if workerCount > 5 {
   314  		workerCount = 5
   315  	}
   316  	var (
   317  		wg           = &sync.WaitGroup{}
   318  		imageData    = make(chan v1Image, workerCount*2)
   319  		imagesToPush = make(chan string, workerCount*2)
   320  		pushes       = make(chan map[string]struct{}, 1)
   321  	)
   322  	for i := 0; i < workerCount; i++ {
   323  		wg.Add(1)
   324  		go p.lookupImageOnEndpoint(wg, endpoint, imageData, imagesToPush)
   325  	}
   326  	// start a go routine that consumes the images to push
   327  	go func() {
   328  		shouldPush := make(map[string]struct{})
   329  		for id := range imagesToPush {
   330  			shouldPush[id] = struct{}{}
   331  		}
   332  		pushes <- shouldPush
   333  	}()
   334  	for _, v1Image := range imageList {
   335  		imageData <- v1Image
   336  	}
   337  	// close the channel to notify the workers that there will be no more images to check.
   338  	close(imageData)
   339  	wg.Wait()
   340  	close(imagesToPush)
   341  	// wait for all the images that require pushes to be collected into a consumable map.
   342  	shouldPush := <-pushes
   343  	// finish by pushing any images and tags to the endpoint.  The order that the images are pushed
   344  	// is very important that is why we are still iterating over the ordered list of imageIDs.
   345  	for _, img := range imageList {
   346  		v1ID := img.V1ID()
   347  		if _, push := shouldPush[v1ID]; push {
   348  			if _, err := p.pushImage(ctx, img, endpoint); err != nil {
   349  				// FIXME: Continue on error?
   350  				return err
   351  			}
   352  		}
   353  		if topImage, isTopImage := img.(*v1TopImage); isTopImage {
   354  			for _, tag := range tags[topImage.imageID] {
   355  				progress.Messagef(p.config.ProgressOutput, "", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(v1ID), endpoint+"repositories/"+reference.Path(p.repoInfo.Name)+"/tags/"+tag)
   356  				if err := p.session.PushRegistryTag(p.repoInfo.Name, v1ID, tag, endpoint); err != nil {
   357  					return err
   358  				}
   359  			}
   360  		}
   361  	}
   362  	return nil
   363  }
   364  
   365  // pushRepository pushes layers that do not already exist on the registry.
   366  func (p *v1Pusher) pushRepository(ctx context.Context) error {
   367  	imgList, tags, referencedLayers, err := p.getImageList()
   368  	defer func() {
   369  		for _, l := range referencedLayers {
   370  			l.Release()
   371  		}
   372  	}()
   373  	if err != nil {
   374  		return err
   375  	}
   376  
   377  	imageIndex := createImageIndex(imgList, tags)
   378  	for _, data := range imageIndex {
   379  		logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
   380  	}
   381  
   382  	// Register all the images in a repository with the registry
   383  	// If an image is not in this list it will not be associated with the repository
   384  	repoData, err := p.session.PushImageJSONIndex(p.repoInfo.Name, imageIndex, false, nil)
   385  	if err != nil {
   386  		return err
   387  	}
   388  	// push the repository to each of the endpoints only if it does not exist.
   389  	for _, endpoint := range repoData.Endpoints {
   390  		if err := p.pushImageToEndpoint(ctx, endpoint, imgList, tags, repoData); err != nil {
   391  			return err
   392  		}
   393  	}
   394  	_, err = p.session.PushImageJSONIndex(p.repoInfo.Name, imageIndex, true, repoData.Endpoints)
   395  	return err
   396  }
   397  
   398  func (p *v1Pusher) pushImage(ctx context.Context, v1Image v1Image, ep string) (checksum string, err error) {
   399  	l := v1Image.Layer()
   400  	v1ID := v1Image.V1ID()
   401  	truncID := stringid.TruncateID(l.DiffID().String())
   402  
   403  	jsonRaw := v1Image.Config()
   404  	progress.Update(p.config.ProgressOutput, truncID, "Pushing")
   405  
   406  	// General rule is to use ID for graph accesses and compatibilityID for
   407  	// calls to session.registry()
   408  	imgData := &registry.ImgData{
   409  		ID: v1ID,
   410  	}
   411  
   412  	// Send the json
   413  	if err := p.session.PushImageJSONRegistry(imgData, jsonRaw, ep); err != nil {
   414  		if err == registry.ErrAlreadyExists {
   415  			progress.Update(p.config.ProgressOutput, truncID, "Image already pushed, skipping")
   416  			return "", nil
   417  		}
   418  		return "", err
   419  	}
   420  
   421  	arch, err := l.TarStream()
   422  	if err != nil {
   423  		return "", err
   424  	}
   425  	defer arch.Close()
   426  
   427  	// don't care if this fails; best effort
   428  	size, _ := l.DiffSize()
   429  
   430  	// Send the layer
   431  	logrus.Debugf("rendered layer for %s of [%d] size", v1ID, size)
   432  
   433  	reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), p.config.ProgressOutput, size, truncID, "Pushing")
   434  	defer reader.Close()
   435  
   436  	checksum, checksumPayload, err := p.session.PushImageLayerRegistry(v1ID, reader, ep, jsonRaw)
   437  	if err != nil {
   438  		return "", err
   439  	}
   440  	imgData.Checksum = checksum
   441  	imgData.ChecksumPayload = checksumPayload
   442  	// Send the checksum
   443  	if err := p.session.PushImageChecksumRegistry(imgData, ep); err != nil {
   444  		return "", err
   445  	}
   446  
   447  	if err := p.v1IDService.Set(v1ID, p.repoInfo.Index.Name, l.DiffID()); err != nil {
   448  		logrus.Warnf("Could not set v1 ID mapping: %v", err)
   449  	}
   450  
   451  	progress.Update(p.config.ProgressOutput, truncID, "Image successfully pushed")
   452  	return imgData.Checksum, nil
   453  }