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