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