github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 Upload 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 err
    68  	}
    69  
    70  	if err = p.pushV2Repository(ctx); err != nil {
    71  		if continueOnError(err) {
    72  			return fallbackError{
    73  				err:         err,
    74  				confirmedV2: p.pushState.confirmedV2,
    75  				transportOK: true,
    76  			}
    77  		}
    78  	}
    79  	return err
    80  }
    81  
    82  func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
    83  	if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged {
    84  		imageID, err := p.config.ReferenceStore.Get(p.ref)
    85  		if err != nil {
    86  			return fmt.Errorf("tag does not exist: %s", p.ref.String())
    87  		}
    88  
    89  		return p.pushV2Tag(ctx, namedTagged, imageID)
    90  	}
    91  
    92  	if !reference.IsNameOnly(p.ref) {
    93  		return errors.New("cannot push a digest reference")
    94  	}
    95  
    96  	// Pull all tags
    97  	pushed := 0
    98  	for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) {
    99  		if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged {
   100  			pushed++
   101  			if err := p.pushV2Tag(ctx, namedTagged, association.ImageID); err != nil {
   102  				return err
   103  			}
   104  		}
   105  	}
   106  
   107  	if pushed == 0 {
   108  		return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, imageID image.ID) error {
   115  	logrus.Debugf("Pushing repository: %s", ref.String())
   116  
   117  	img, err := p.config.ImageStore.Get(imageID)
   118  	if err != nil {
   119  		return fmt.Errorf("could not find image from tag %s: %v", ref.String(), err)
   120  	}
   121  
   122  	var l layer.Layer
   123  
   124  	topLayerID := img.RootFS.ChainID()
   125  	if topLayerID == "" {
   126  		l = layer.EmptyLayer
   127  	} else {
   128  		l, err = p.config.LayerStore.Get(topLayerID)
   129  		if err != nil {
   130  			return fmt.Errorf("failed to get top layer from image: %v", err)
   131  		}
   132  		defer layer.ReleaseAndLog(p.config.LayerStore, l)
   133  	}
   134  
   135  	var descriptors []xfer.UploadDescriptor
   136  
   137  	descriptorTemplate := v2PushDescriptor{
   138  		v2MetadataService: p.v2MetadataService,
   139  		repoInfo:          p.repoInfo,
   140  		repo:              p.repo,
   141  		pushState:         &p.pushState,
   142  	}
   143  
   144  	// Loop bounds condition is to avoid pushing the base layer on Windows.
   145  	for i := 0; i < len(img.RootFS.DiffIDs); i++ {
   146  		descriptor := descriptorTemplate
   147  		descriptor.layer = l
   148  		descriptors = append(descriptors, &descriptor)
   149  
   150  		l = l.Parent()
   151  	}
   152  
   153  	if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil {
   154  		return err
   155  	}
   156  
   157  	// Try schema2 first
   158  	builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), img.RawJSON())
   159  	manifest, err := manifestFromBuilder(ctx, builder, descriptors)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	manSvc, err := p.repo.Manifests(ctx)
   165  	if err != nil {
   166  		return err
   167  	}
   168  
   169  	putOptions := []distribution.ManifestServiceOption{distribution.WithTag(ref.Tag())}
   170  	if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
   171  		logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
   172  
   173  		manifestRef, err := distreference.WithTag(p.repo.Named(), ref.Tag())
   174  		if err != nil {
   175  			return err
   176  		}
   177  		builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, manifestRef, img.RawJSON())
   178  		manifest, err = manifestFromBuilder(ctx, builder, descriptors)
   179  		if err != nil {
   180  			return err
   181  		}
   182  
   183  		if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	var canonicalManifest []byte
   189  
   190  	switch v := manifest.(type) {
   191  	case *schema1.SignedManifest:
   192  		canonicalManifest = v.Canonical
   193  	case *schema2.DeserializedManifest:
   194  		_, canonicalManifest, err = v.Payload()
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  
   200  	manifestDigest := digest.FromBytes(canonicalManifest)
   201  	progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest))
   202  	// Signal digest to the trust client so it can sign the
   203  	// push, if appropriate.
   204  	progress.Aux(p.config.ProgressOutput, PushResult{Tag: ref.Tag(), Digest: manifestDigest, Size: len(canonicalManifest)})
   205  
   206  	return nil
   207  }
   208  
   209  func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) {
   210  	// descriptors is in reverse order; iterate backwards to get references
   211  	// appended in the right order.
   212  	for i := len(descriptors) - 1; i >= 0; i-- {
   213  		if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil {
   214  			return nil, err
   215  		}
   216  	}
   217  
   218  	return builder.Build(ctx)
   219  }
   220  
   221  type v2PushDescriptor struct {
   222  	layer             layer.Layer
   223  	v2MetadataService *metadata.V2MetadataService
   224  	repoInfo          reference.Named
   225  	repo              distribution.Repository
   226  	pushState         *pushState
   227  	remoteDescriptor  distribution.Descriptor
   228  }
   229  
   230  func (pd *v2PushDescriptor) Key() string {
   231  	return "v2push:" + pd.repo.Named().Name() + " " + pd.layer.DiffID().String()
   232  }
   233  
   234  func (pd *v2PushDescriptor) ID() string {
   235  	return stringid.TruncateID(pd.layer.DiffID().String())
   236  }
   237  
   238  func (pd *v2PushDescriptor) DiffID() layer.DiffID {
   239  	return pd.layer.DiffID()
   240  }
   241  
   242  func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) {
   243  	diffID := pd.DiffID()
   244  
   245  	pd.pushState.Lock()
   246  	if descriptor, ok := pd.pushState.remoteLayers[diffID]; ok {
   247  		// it is already known that the push is not needed and
   248  		// therefore doing a stat is unnecessary
   249  		pd.pushState.Unlock()
   250  		progress.Update(progressOutput, pd.ID(), "Layer already exists")
   251  		return descriptor, nil
   252  	}
   253  	pd.pushState.Unlock()
   254  
   255  	// Do we have any metadata associated with this layer's DiffID?
   256  	v2Metadata, err := pd.v2MetadataService.GetMetadata(diffID)
   257  	if err == nil {
   258  		descriptor, exists, err := layerAlreadyExists(ctx, v2Metadata, pd.repoInfo, pd.repo, pd.pushState)
   259  		if err != nil {
   260  			progress.Update(progressOutput, pd.ID(), "Image push failed")
   261  			return distribution.Descriptor{}, retryOnError(err)
   262  		}
   263  		if exists {
   264  			progress.Update(progressOutput, pd.ID(), "Layer already exists")
   265  			pd.pushState.Lock()
   266  			pd.pushState.remoteLayers[diffID] = descriptor
   267  			pd.pushState.Unlock()
   268  			return descriptor, nil
   269  		}
   270  	}
   271  
   272  	logrus.Debugf("Pushing layer: %s", diffID)
   273  
   274  	// if digest was empty or not saved, or if blob does not exist on the remote repository,
   275  	// then push the blob.
   276  	bs := pd.repo.Blobs(ctx)
   277  
   278  	var layerUpload distribution.BlobWriter
   279  	mountAttemptsRemaining := 3
   280  
   281  	// Attempt to find another repository in the same registry to mount the layer
   282  	// from to avoid an unnecessary upload.
   283  	// Note: metadata is stored from oldest to newest, so we iterate through this
   284  	// slice in reverse to maximize our chances of the blob still existing in the
   285  	// remote repository.
   286  	for i := len(v2Metadata) - 1; i >= 0 && mountAttemptsRemaining > 0; i-- {
   287  		mountFrom := v2Metadata[i]
   288  
   289  		sourceRepo, err := reference.ParseNamed(mountFrom.SourceRepository)
   290  		if err != nil {
   291  			continue
   292  		}
   293  		if pd.repoInfo.Hostname() != sourceRepo.Hostname() {
   294  			// don't mount blobs from another registry
   295  			continue
   296  		}
   297  
   298  		namedRef, err := reference.WithName(mountFrom.SourceRepository)
   299  		if err != nil {
   300  			continue
   301  		}
   302  
   303  		// TODO (brianbland): We need to construct a reference where the Name is
   304  		// only the full remote name, so clean this up when distribution has a
   305  		// richer reference package
   306  		remoteRef, err := distreference.WithName(namedRef.RemoteName())
   307  		if err != nil {
   308  			continue
   309  		}
   310  
   311  		canonicalRef, err := distreference.WithDigest(remoteRef, mountFrom.Digest)
   312  		if err != nil {
   313  			continue
   314  		}
   315  
   316  		logrus.Debugf("attempting to mount layer %s (%s) from %s", diffID, mountFrom.Digest, sourceRepo.FullName())
   317  
   318  		layerUpload, err = bs.Create(ctx, client.WithMountFrom(canonicalRef))
   319  		switch err := err.(type) {
   320  		case distribution.ErrBlobMounted:
   321  			progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name())
   322  
   323  			err.Descriptor.MediaType = schema2.MediaTypeLayer
   324  
   325  			pd.pushState.Lock()
   326  			pd.pushState.confirmedV2 = true
   327  			pd.pushState.remoteLayers[diffID] = err.Descriptor
   328  			pd.pushState.Unlock()
   329  
   330  			// Cache mapping from this layer's DiffID to the blobsum
   331  			if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: mountFrom.Digest, SourceRepository: pd.repoInfo.FullName()}); err != nil {
   332  				return distribution.Descriptor{}, xfer.DoNotRetry{Err: err}
   333  			}
   334  			return err.Descriptor, nil
   335  		case nil:
   336  			// blob upload session created successfully, so begin the upload
   337  			mountAttemptsRemaining = 0
   338  		default:
   339  			// unable to mount layer from this repository, so this source mapping is no longer valid
   340  			logrus.Debugf("unassociating layer %s (%s) with %s", diffID, mountFrom.Digest, mountFrom.SourceRepository)
   341  			pd.v2MetadataService.Remove(mountFrom)
   342  			mountAttemptsRemaining--
   343  		}
   344  	}
   345  
   346  	if layerUpload == nil {
   347  		layerUpload, err = bs.Create(ctx)
   348  		if err != nil {
   349  			return distribution.Descriptor{}, retryOnError(err)
   350  		}
   351  	}
   352  	defer layerUpload.Close()
   353  
   354  	arch, err := pd.layer.TarStream()
   355  	if err != nil {
   356  		return distribution.Descriptor{}, xfer.DoNotRetry{Err: err}
   357  	}
   358  
   359  	// don't care if this fails; best effort
   360  	size, _ := pd.layer.DiffSize()
   361  
   362  	reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), progressOutput, size, pd.ID(), "Pushing")
   363  	compressedReader, compressionDone := compress(reader)
   364  	defer func() {
   365  		reader.Close()
   366  		<-compressionDone
   367  	}()
   368  
   369  	digester := digest.Canonical.New()
   370  	tee := io.TeeReader(compressedReader, digester.Hash())
   371  
   372  	nn, err := layerUpload.ReadFrom(tee)
   373  	compressedReader.Close()
   374  	if err != nil {
   375  		return distribution.Descriptor{}, retryOnError(err)
   376  	}
   377  
   378  	pushDigest := digester.Digest()
   379  	if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil {
   380  		return distribution.Descriptor{}, retryOnError(err)
   381  	}
   382  
   383  	logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
   384  	progress.Update(progressOutput, pd.ID(), "Pushed")
   385  
   386  	// Cache mapping from this layer's DiffID to the blobsum
   387  	if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: pushDigest, SourceRepository: pd.repoInfo.FullName()}); err != nil {
   388  		return distribution.Descriptor{}, xfer.DoNotRetry{Err: err}
   389  	}
   390  
   391  	pd.pushState.Lock()
   392  
   393  	// If Commit succeeded, that's an indication that the remote registry
   394  	// speaks the v2 protocol.
   395  	pd.pushState.confirmedV2 = true
   396  
   397  	descriptor := distribution.Descriptor{
   398  		Digest:    pushDigest,
   399  		MediaType: schema2.MediaTypeLayer,
   400  		Size:      nn,
   401  	}
   402  	pd.pushState.remoteLayers[diffID] = descriptor
   403  
   404  	pd.pushState.Unlock()
   405  
   406  	return descriptor, nil
   407  }
   408  
   409  func (pd *v2PushDescriptor) SetRemoteDescriptor(descriptor distribution.Descriptor) {
   410  	pd.remoteDescriptor = descriptor
   411  }
   412  
   413  func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor {
   414  	return pd.remoteDescriptor
   415  }
   416  
   417  // layerAlreadyExists checks if the registry already know about any of the
   418  // metadata passed in the "metadata" slice. If it finds one that the registry
   419  // knows about, it returns the known digest and "true".
   420  func layerAlreadyExists(ctx context.Context, metadata []metadata.V2Metadata, repoInfo reference.Named, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) {
   421  	for _, meta := range metadata {
   422  		// Only check blobsums that are known to this repository or have an unknown source
   423  		if meta.SourceRepository != "" && meta.SourceRepository != repoInfo.FullName() {
   424  			continue
   425  		}
   426  		descriptor, err := repo.Blobs(ctx).Stat(ctx, meta.Digest)
   427  		switch err {
   428  		case nil:
   429  			descriptor.MediaType = schema2.MediaTypeLayer
   430  			return descriptor, true, nil
   431  		case distribution.ErrBlobUnknown:
   432  			// nop
   433  		default:
   434  			return distribution.Descriptor{}, false, err
   435  		}
   436  	}
   437  	return distribution.Descriptor{}, false, nil
   438  }