github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/distribution/push_v1.go (about)

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