github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/distribution/push.go (about)

     1  package distribution
     2  
     3  import (
     4  	"bufio"
     5  	"compress/gzip"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/distribution/metadata"
    11  	"github.com/docker/docker/distribution/xfer"
    12  	"github.com/docker/docker/image"
    13  	"github.com/docker/docker/layer"
    14  	"github.com/docker/docker/pkg/progress"
    15  	"github.com/docker/docker/reference"
    16  	"github.com/docker/docker/registry"
    17  	"github.com/docker/engine-api/types"
    18  	"github.com/docker/libtrust"
    19  	"golang.org/x/net/context"
    20  )
    21  
    22  // ImagePushConfig stores push configuration.
    23  type ImagePushConfig struct {
    24  	// MetaHeaders store HTTP headers with metadata about the image
    25  	// (DockerHeaders with prefix X-Meta- in the request).
    26  	MetaHeaders map[string][]string
    27  	// AuthConfig holds authentication credentials for authenticating with
    28  	// the registry.
    29  	AuthConfig *types.AuthConfig
    30  	// ProgressOutput is the interface for showing the status of the push
    31  	// operation.
    32  	ProgressOutput progress.Output
    33  	// RegistryService is the registry service to use for TLS configuration
    34  	// and endpoint lookup.
    35  	RegistryService *registry.Service
    36  	// ImageEventLogger notifies events for a given image
    37  	ImageEventLogger func(id, name, action string)
    38  	// MetadataStore is the storage backend for distribution-specific
    39  	// metadata.
    40  	MetadataStore metadata.Store
    41  	// LayerStore manages layers.
    42  	LayerStore layer.Store
    43  	// ImageStore manages images.
    44  	ImageStore image.Store
    45  	// ReferenceStore manages tags.
    46  	ReferenceStore reference.Store
    47  	// TrustKey is the private key for legacy signatures. This is typically
    48  	// an ephemeral key, since these signatures are no longer verified.
    49  	TrustKey libtrust.PrivateKey
    50  	// UploadManager dispatches uploads.
    51  	UploadManager *xfer.LayerUploadManager
    52  }
    53  
    54  // Pusher is an interface that abstracts pushing for different API versions.
    55  type Pusher interface {
    56  	// Push tries to push the image configured at the creation of Pusher.
    57  	// Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint.
    58  	//
    59  	// TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic.
    60  	Push(ctx context.Context) error
    61  }
    62  
    63  const compressionBufSize = 32768
    64  
    65  // NewPusher creates a new Pusher interface that will push to either a v1 or v2
    66  // registry. The endpoint argument contains a Version field that determines
    67  // whether a v1 or v2 pusher will be created. The other parameters are passed
    68  // through to the underlying pusher implementation for use during the actual
    69  // push operation.
    70  func NewPusher(ref reference.Named, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePushConfig *ImagePushConfig) (Pusher, error) {
    71  	switch endpoint.Version {
    72  	case registry.APIVersion2:
    73  		return &v2Pusher{
    74  			blobSumService: metadata.NewBlobSumService(imagePushConfig.MetadataStore),
    75  			ref:            ref,
    76  			endpoint:       endpoint,
    77  			repoInfo:       repoInfo,
    78  			config:         imagePushConfig,
    79  		}, nil
    80  	case registry.APIVersion1:
    81  		return &v1Pusher{
    82  			v1IDService: metadata.NewV1IDService(imagePushConfig.MetadataStore),
    83  			ref:         ref,
    84  			endpoint:    endpoint,
    85  			repoInfo:    repoInfo,
    86  			config:      imagePushConfig,
    87  		}, nil
    88  	}
    89  	return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
    90  }
    91  
    92  // Push initiates a push operation on the repository named localName.
    93  // ref is the specific variant of the image to be pushed.
    94  // If no tag is provided, all tags will be pushed.
    95  func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushConfig) error {
    96  	// FIXME: Allow to interrupt current push when new push of same image is done.
    97  
    98  	// Resolve the Repository name from fqn to RepositoryInfo
    99  	repoInfo, err := imagePushConfig.RegistryService.ResolveRepository(ref)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(repoInfo)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	progress.Messagef(imagePushConfig.ProgressOutput, "", "The push refers to a repository [%s]", repoInfo.FullName())
   110  
   111  	associations := imagePushConfig.ReferenceStore.ReferencesByName(repoInfo)
   112  	if len(associations) == 0 {
   113  		return fmt.Errorf("Repository does not exist: %s", repoInfo.Name())
   114  	}
   115  
   116  	var (
   117  		lastErr error
   118  
   119  		// confirmedV2 is set to true if a push attempt managed to
   120  		// confirm that it was talking to a v2 registry. This will
   121  		// prevent fallback to the v1 protocol.
   122  		confirmedV2 bool
   123  	)
   124  
   125  	for _, endpoint := range endpoints {
   126  		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
   127  			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
   128  			continue
   129  		}
   130  
   131  		logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
   132  
   133  		pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
   134  		if err != nil {
   135  			lastErr = err
   136  			continue
   137  		}
   138  		if err := pusher.Push(ctx); err != nil {
   139  			// Was this push cancelled? If so, don't try to fall
   140  			// back.
   141  			select {
   142  			case <-ctx.Done():
   143  			default:
   144  				if fallbackErr, ok := err.(fallbackError); ok {
   145  					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
   146  					err = fallbackErr.err
   147  					lastErr = err
   148  					continue
   149  				}
   150  			}
   151  
   152  			logrus.Debugf("Not continuing with error: %v", err)
   153  			return err
   154  		}
   155  
   156  		imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push")
   157  		return nil
   158  	}
   159  
   160  	if lastErr == nil {
   161  		lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.FullName())
   162  	}
   163  	return lastErr
   164  }
   165  
   166  // compress returns an io.ReadCloser which will supply a compressed version of
   167  // the provided Reader. The caller must close the ReadCloser after reading the
   168  // compressed data.
   169  //
   170  // Note that this function returns a reader instead of taking a writer as an
   171  // argument so that it can be used with httpBlobWriter's ReadFrom method.
   172  // Using httpBlobWriter's Write method would send a PATCH request for every
   173  // Write call.
   174  func compress(in io.Reader) io.ReadCloser {
   175  	pipeReader, pipeWriter := io.Pipe()
   176  	// Use a bufio.Writer to avoid excessive chunking in HTTP request.
   177  	bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize)
   178  	compressor := gzip.NewWriter(bufWriter)
   179  
   180  	go func() {
   181  		_, err := io.Copy(compressor, in)
   182  		if err == nil {
   183  			err = compressor.Close()
   184  		}
   185  		if err == nil {
   186  			err = bufWriter.Flush()
   187  		}
   188  		if err != nil {
   189  			pipeWriter.CloseWithError(err)
   190  		} else {
   191  			pipeWriter.Close()
   192  		}
   193  	}()
   194  
   195  	return pipeReader
   196  }