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

     1  package distribution
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/distribution"
    11  	"github.com/docker/distribution/digest"
    12  	"github.com/docker/distribution/manifest/schema1"
    13  	"github.com/docker/distribution/manifest/schema2"
    14  	distreference "github.com/docker/distribution/reference"
    15  	"github.com/docker/distribution/registry/client"
    16  	"github.com/docker/docker/distribution/metadata"
    17  	"github.com/docker/docker/distribution/xfer"
    18  	"github.com/docker/docker/image"
    19  	"github.com/docker/docker/layer"
    20  	"github.com/docker/docker/pkg/ioutils"
    21  	"github.com/docker/docker/pkg/progress"
    22  	"github.com/docker/docker/pkg/stringid"
    23  	"github.com/docker/docker/reference"
    24  	"github.com/docker/docker/registry"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  // PushResult contains the tag, manifest digest, and manifest size from the
    29  // push. It's used to signal this information to the trust code in the client
    30  // so it can sign the manifest if necessary.
    31  type PushResult struct {
    32  	Tag    string
    33  	Digest digest.Digest
    34  	Size   int
    35  }
    36  
    37  type v2Pusher struct {
    38  	v2MetadataService *metadata.V2MetadataService
    39  	ref               reference.Named
    40  	endpoint          registry.APIEndpoint
    41  	repoInfo          *registry.RepositoryInfo
    42  	config            *ImagePushConfig
    43  	repo              distribution.Repository
    44  
    45  	// pushState is state built by the Download functions.
    46  	pushState pushState
    47  }
    48  
    49  type pushState struct {
    50  	sync.Mutex
    51  	// remoteLayers is the set of layers known to exist on the remote side.
    52  	// This avoids redundant queries when pushing multiple tags that
    53  	// involve the same layers. It is also used to fill in digest and size
    54  	// information when building the manifest.
    55  	remoteLayers map[layer.DiffID]distribution.Descriptor
    56  	// confirmedV2 is set to true if we confirm we're talking to a v2
    57  	// registry. This is used to limit fallbacks to the v1 protocol.
    58  	confirmedV2 bool
    59  }
    60  
    61  func (p *v2Pusher) Push(ctx context.Context) (err error) {
    62  	p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor)
    63  
    64  	p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
    65  	if err != nil {
    66  		logrus.Debugf("Error getting v2 registry: %v", err)
    67  		return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
    68  	}
    69  
    70  	if err = p.pushV2Repository(ctx); err != nil {
    71  		if registry.ContinueOnError(err) {
    72  			return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
    73  		}
    74  	}
    75  	return err
    76  }
    77  
    78  func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
    79  	if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged {
    80  		imageID, err := p.config.ReferenceStore.Get(p.ref)
    81  		if err != nil {
    82  			return fmt.Errorf("tag does not exist: %s", p.ref.String())
    83  		}
    84  
    85  		return p.pushV2Tag(ctx, namedTagged, imageID)
    86  	}
    87  
    88  	if !reference.IsNameOnly(p.ref) {
    89  		return errors.New("cannot push a digest reference")
    90  	}
    91  
    92  	// Pull all tags
    93  	pushed := 0
    94  	for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) {
    95  		if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged {
    96  			pushed++
    97  			if err := p.pushV2Tag(ctx, namedTagged, association.ImageID); err != nil {
    98  				return err
    99  			}
   100  		}
   101  	}
   102  
   103  	if pushed == 0 {
   104  		return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, imageID image.ID) error {
   111  	logrus.Debugf("Pushing repository: %s", ref.String())
   112  
   113  	img, err := p.config.ImageStore.Get(imageID)
   114  	if err != nil {
   115  		return fmt.Errorf("could not find image from tag %s: %v", ref.String(), err)
   116  	}
   117  
   118  	var l layer.Layer
   119  
   120  	topLayerID := img.RootFS.ChainID()
   121  	if topLayerID == "" {
   122  		l = layer.EmptyLayer
   123  	} else {
   124  		l, err = p.config.LayerStore.Get(topLayerID)
   125  		if err != nil {
   126  			return fmt.Errorf("failed to get top layer from image: %v", err)
   127  		}
   128  		defer layer.ReleaseAndLog(p.config.LayerStore, l)
   129  	}
   130  
   131  	var descriptors []xfer.UploadDescriptor
   132  
   133  	descriptorTemplate := v2PushDescriptor{
   134  		v2MetadataService: p.v2MetadataService,
   135  		repoInfo:          p.repoInfo,
   136  		repo:              p.repo,
   137  		pushState:         &p.pushState,
   138  	}
   139  
   140  	// Loop bounds condition is to avoid pushing the base layer on Windows.
   141  	for i := 0; i < len(img.RootFS.DiffIDs); i++ {
   142  		descriptor := descriptorTemplate
   143  		descriptor.layer = l
   144  		descriptors = append(descriptors, &descriptor)
   145  
   146  		l = l.Parent()
   147  	}
   148  
   149  	if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil {
   150  		return err
   151  	}
   152  
   153  	// Try schema2 first
   154  	builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), img.RawJSON())
   155  	manifest, err := manifestFromBuilder(ctx, builder, descriptors)
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	manSvc, err := p.repo.Manifests(ctx)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	putOptions := []distribution.ManifestServiceOption{client.WithTag(ref.Tag())}
   166  	if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
   167  		logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
   168  
   169  		builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, p.repo.Name(), ref.Tag(), img.RawJSON())
   170  		manifest, err = manifestFromBuilder(ctx, builder, descriptors)
   171  		if err != nil {
   172  			return err
   173  		}
   174  
   175  		if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
   176  			return err
   177  		}
   178  	}
   179  
   180  	var canonicalManifest []byte
   181  
   182  	switch v := manifest.(type) {
   183  	case *schema1.SignedManifest:
   184  		canonicalManifest = v.Canonical
   185  	case *schema2.DeserializedManifest:
   186  		_, canonicalManifest, err = v.Payload()
   187  		if err != nil {
   188  			return err
   189  		}
   190  	}
   191  
   192  	manifestDigest := digest.FromBytes(canonicalManifest)
   193  	progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest))
   194  	// Signal digest to the trust client so it can sign the
   195  	// push, if appropriate.
   196  	progress.Aux(p.config.ProgressOutput, PushResult{Tag: ref.Tag(), Digest: manifestDigest, Size: len(canonicalManifest)})
   197  
   198  	return nil
   199  }
   200  
   201  func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) {
   202  	// descriptors is in reverse order; iterate backwards to get references
   203  	// appended in the right order.
   204  	for i := len(descriptors) - 1; i >= 0; i-- {
   205  		if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil {
   206  			return nil, err
   207  		}
   208  	}
   209  
   210  	return builder.Build(ctx)
   211  }
   212  
   213  type v2PushDescriptor struct {
   214  	layer             layer.Layer
   215  	v2MetadataService *metadata.V2MetadataService
   216  	repoInfo          reference.Named
   217  	repo              distribution.Repository
   218  	pushState         *pushState
   219  }
   220  
   221  func (pd *v2PushDescriptor) Key() string {
   222  	return "v2push:" + pd.repo.Name() + " " + pd.layer.DiffID().String()
   223  }
   224  
   225  func (pd *v2PushDescriptor) ID() string {
   226  	return stringid.TruncateID(pd.layer.DiffID().String())
   227  }
   228  
   229  func (pd *v2PushDescriptor) DiffID() layer.DiffID {
   230  	return pd.layer.DiffID()
   231  }
   232  
   233  func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) error {
   234  	diffID := pd.DiffID()
   235  
   236  	pd.pushState.Lock()
   237  	if _, ok := pd.pushState.remoteLayers[diffID]; ok {
   238  		// it is already known that the push is not needed and
   239  		// therefore doing a stat is unnecessary
   240  		pd.pushState.Unlock()
   241  		progress.Update(progressOutput, pd.ID(), "Layer already exists")
   242  		return nil
   243  	}
   244  	pd.pushState.Unlock()
   245  
   246  	// Do we have any metadata associated with this layer's DiffID?
   247  	v2Metadata, err := pd.v2MetadataService.GetMetadata(diffID)
   248  	if err == nil {
   249  		descriptor, exists, err := layerAlreadyExists(ctx, v2Metadata, pd.repoInfo, pd.repo, pd.pushState)
   250  		if err != nil {
   251  			progress.Update(progressOutput, pd.ID(), "Image push failed")
   252  			return retryOnError(err)
   253  		}
   254  		if exists {
   255  			progress.Update(progressOutput, pd.ID(), "Layer already exists")
   256  			pd.pushState.Lock()
   257  			pd.pushState.remoteLayers[diffID] = descriptor
   258  			pd.pushState.Unlock()
   259  			return nil
   260  		}
   261  	}
   262  
   263  	logrus.Debugf("Pushing layer: %s", diffID)
   264  
   265  	// if digest was empty or not saved, or if blob does not exist on the remote repository,
   266  	// then push the blob.
   267  	bs := pd.repo.Blobs(ctx)
   268  
   269  	var mountFrom metadata.V2Metadata
   270  
   271  	// Attempt to find another repository in the same registry to mount the layer from to avoid an unnecessary upload
   272  	for _, metadata := range v2Metadata {
   273  		sourceRepo, err := reference.ParseNamed(metadata.SourceRepository)
   274  		if err != nil {
   275  			continue
   276  		}
   277  		if pd.repoInfo.Hostname() == sourceRepo.Hostname() {
   278  			logrus.Debugf("attempting to mount layer %s (%s) from %s", diffID, metadata.Digest, sourceRepo.FullName())
   279  			mountFrom = metadata
   280  			break
   281  		}
   282  	}
   283  
   284  	var createOpts []distribution.BlobCreateOption
   285  
   286  	if mountFrom.SourceRepository != "" {
   287  		namedRef, err := reference.WithName(mountFrom.SourceRepository)
   288  		if err != nil {
   289  			return err
   290  		}
   291  
   292  		// TODO (brianbland): We need to construct a reference where the Name is
   293  		// only the full remote name, so clean this up when distribution has a
   294  		// richer reference package
   295  		remoteRef, err := distreference.WithName(namedRef.RemoteName())
   296  		if err != nil {
   297  			return err
   298  		}
   299  
   300  		canonicalRef, err := distreference.WithDigest(remoteRef, mountFrom.Digest)
   301  		if err != nil {
   302  			return err
   303  		}
   304  
   305  		createOpts = append(createOpts, client.WithMountFrom(canonicalRef))
   306  	}
   307  
   308  	// Send the layer
   309  	layerUpload, err := bs.Create(ctx, createOpts...)
   310  	switch err := err.(type) {
   311  	case distribution.ErrBlobMounted:
   312  		progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name())
   313  
   314  		err.Descriptor.MediaType = schema2.MediaTypeLayer
   315  
   316  		pd.pushState.Lock()
   317  		pd.pushState.confirmedV2 = true
   318  		pd.pushState.remoteLayers[diffID] = err.Descriptor
   319  		pd.pushState.Unlock()
   320  
   321  		// Cache mapping from this layer's DiffID to the blobsum
   322  		if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: mountFrom.Digest, SourceRepository: pd.repoInfo.FullName()}); err != nil {
   323  			return xfer.DoNotRetry{Err: err}
   324  		}
   325  
   326  		return nil
   327  	}
   328  	if mountFrom.SourceRepository != "" {
   329  		// unable to mount layer from this repository, so this source mapping is no longer valid
   330  		logrus.Debugf("unassociating layer %s (%s) with %s", diffID, mountFrom.Digest, mountFrom.SourceRepository)
   331  		pd.v2MetadataService.Remove(mountFrom)
   332  	}
   333  
   334  	if err != nil {
   335  		return retryOnError(err)
   336  	}
   337  	defer layerUpload.Close()
   338  
   339  	arch, err := pd.layer.TarStream()
   340  	if err != nil {
   341  		return xfer.DoNotRetry{Err: err}
   342  	}
   343  
   344  	// don't care if this fails; best effort
   345  	size, _ := pd.layer.DiffSize()
   346  
   347  	reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), progressOutput, size, pd.ID(), "Pushing")
   348  	compressedReader, compressionDone := compress(reader)
   349  	defer func() {
   350  		reader.Close()
   351  		<-compressionDone
   352  	}()
   353  
   354  	digester := digest.Canonical.New()
   355  	tee := io.TeeReader(compressedReader, digester.Hash())
   356  
   357  	nn, err := layerUpload.ReadFrom(tee)
   358  	compressedReader.Close()
   359  	if err != nil {
   360  		return retryOnError(err)
   361  	}
   362  
   363  	pushDigest := digester.Digest()
   364  	if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil {
   365  		return retryOnError(err)
   366  	}
   367  
   368  	logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
   369  	progress.Update(progressOutput, pd.ID(), "Pushed")
   370  
   371  	// Cache mapping from this layer's DiffID to the blobsum
   372  	if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: pushDigest, SourceRepository: pd.repoInfo.FullName()}); err != nil {
   373  		return xfer.DoNotRetry{Err: err}
   374  	}
   375  
   376  	pd.pushState.Lock()
   377  
   378  	// If Commit succeded, that's an indication that the remote registry
   379  	// speaks the v2 protocol.
   380  	pd.pushState.confirmedV2 = true
   381  
   382  	pd.pushState.remoteLayers[diffID] = distribution.Descriptor{
   383  		Digest:    pushDigest,
   384  		MediaType: schema2.MediaTypeLayer,
   385  		Size:      nn,
   386  	}
   387  
   388  	pd.pushState.Unlock()
   389  
   390  	return nil
   391  }
   392  
   393  func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor {
   394  	// Not necessary to lock pushStatus because this is always
   395  	// called after all the mutation in pushStatus.
   396  	// By the time this function is called, every layer will have
   397  	// an entry in remoteLayers.
   398  	return pd.pushState.remoteLayers[pd.DiffID()]
   399  }
   400  
   401  // layerAlreadyExists checks if the registry already know about any of the
   402  // metadata passed in the "metadata" slice. If it finds one that the registry
   403  // knows about, it returns the known digest and "true".
   404  func layerAlreadyExists(ctx context.Context, metadata []metadata.V2Metadata, repoInfo reference.Named, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) {
   405  	for _, meta := range metadata {
   406  		// Only check blobsums that are known to this repository or have an unknown source
   407  		if meta.SourceRepository != "" && meta.SourceRepository != repoInfo.FullName() {
   408  			continue
   409  		}
   410  		descriptor, err := repo.Blobs(ctx).Stat(ctx, meta.Digest)
   411  		switch err {
   412  		case nil:
   413  			descriptor.MediaType = schema2.MediaTypeLayer
   414  			return descriptor, true, nil
   415  		case distribution.ErrBlobUnknown:
   416  			// nop
   417  		default:
   418  			return distribution.Descriptor{}, false, err
   419  		}
   420  	}
   421  	return distribution.Descriptor{}, false, nil
   422  }