github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/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  			v2MetadataService: metadata.NewV2MetadataService(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  //
   175  // The second return value is a channel that gets closed when the goroutine
   176  // is finished. This allows the caller to make sure the goroutine finishes
   177  // before it releases any resources connected with the reader that was
   178  // passed in.
   179  func compress(in io.Reader) (io.ReadCloser, chan struct{}) {
   180  	compressionDone := make(chan struct{})
   181  
   182  	pipeReader, pipeWriter := io.Pipe()
   183  	// Use a bufio.Writer to avoid excessive chunking in HTTP request.
   184  	bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize)
   185  	compressor := gzip.NewWriter(bufWriter)
   186  
   187  	go func() {
   188  		_, err := io.Copy(compressor, in)
   189  		if err == nil {
   190  			err = compressor.Close()
   191  		}
   192  		if err == nil {
   193  			err = bufWriter.Flush()
   194  		}
   195  		if err != nil {
   196  			pipeWriter.CloseWithError(err)
   197  		} else {
   198  			pipeWriter.Close()
   199  		}
   200  		close(compressionDone)
   201  	}()
   202  
   203  	return pipeReader, compressionDone
   204  }