github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/distribution/push_v1.go (about)

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