github.com/jfrazelle/docker@v1.1.2-0.20210712172922-bf78e25fe508/distribution/push.go (about)

     1  package distribution // import "github.com/docker/docker/distribution"
     2  
     3  import (
     4  	"bufio"
     5  	"compress/gzip"
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/docker/distribution/reference"
    11  	"github.com/docker/docker/distribution/metadata"
    12  	"github.com/docker/docker/pkg/progress"
    13  	"github.com/docker/docker/registry"
    14  	"github.com/sirupsen/logrus"
    15  )
    16  
    17  // Pusher is an interface that abstracts pushing for different API versions.
    18  type Pusher interface {
    19  	// Push tries to push the image configured at the creation of Pusher.
    20  	// Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint.
    21  	//
    22  	// TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic.
    23  	Push(ctx context.Context) error
    24  }
    25  
    26  const compressionBufSize = 32768
    27  
    28  // NewPusher creates a new Pusher interface that will push to either a v1 or v2
    29  // registry. The endpoint argument contains a Version field that determines
    30  // whether a v1 or v2 pusher will be created. The other parameters are passed
    31  // through to the underlying pusher implementation for use during the actual
    32  // push operation.
    33  func NewPusher(ref reference.Named, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePushConfig *ImagePushConfig) (Pusher, error) {
    34  	switch endpoint.Version {
    35  	case registry.APIVersion2:
    36  		return &v2Pusher{
    37  			v2MetadataService: metadata.NewV2MetadataService(imagePushConfig.MetadataStore),
    38  			ref:               ref,
    39  			endpoint:          endpoint,
    40  			repoInfo:          repoInfo,
    41  			config:            imagePushConfig,
    42  		}, nil
    43  	case registry.APIVersion1:
    44  		return nil, fmt.Errorf("protocol version %d no longer supported. Please contact admins of registry %s", endpoint.Version, endpoint.URL)
    45  	}
    46  	return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL)
    47  }
    48  
    49  // Push initiates a push operation on ref.
    50  // ref is the specific variant of the image to be pushed.
    51  // If no tag is provided, all tags will be pushed.
    52  func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushConfig) error {
    53  	// FIXME: Allow to interrupt current push when new push of same image is done.
    54  
    55  	// Resolve the Repository name from fqn to RepositoryInfo
    56  	repoInfo, err := imagePushConfig.RegistryService.ResolveRepository(ref)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	progress.Messagef(imagePushConfig.ProgressOutput, "", "The push refers to repository [%s]", repoInfo.Name.Name())
    67  
    68  	associations := imagePushConfig.ReferenceStore.ReferencesByName(repoInfo.Name)
    69  	if len(associations) == 0 {
    70  		return fmt.Errorf("An image does not exist locally with the tag: %s", reference.FamiliarName(repoInfo.Name))
    71  	}
    72  
    73  	var (
    74  		lastErr error
    75  
    76  		// confirmedTLSRegistries is a map indicating which registries
    77  		// are known to be using TLS. There should never be a plaintext
    78  		// retry for any of these.
    79  		confirmedTLSRegistries = make(map[string]struct{})
    80  	)
    81  
    82  	for _, endpoint := range endpoints {
    83  		if endpoint.URL.Scheme != "https" {
    84  			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
    85  				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
    86  				continue
    87  			}
    88  		}
    89  
    90  		logrus.Debugf("Trying to push %s to %s %s", repoInfo.Name.Name(), endpoint.URL, endpoint.Version)
    91  
    92  		pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
    93  		if err != nil {
    94  			lastErr = err
    95  			continue
    96  		}
    97  		if err := pusher.Push(ctx); err != nil {
    98  			// Was this push cancelled? If so, don't try to fall
    99  			// back.
   100  			select {
   101  			case <-ctx.Done():
   102  			default:
   103  				if fallbackErr, ok := err.(fallbackError); ok {
   104  					if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
   105  						confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
   106  					}
   107  					err = fallbackErr.err
   108  					lastErr = err
   109  					logrus.Infof("Attempting next endpoint for push after error: %v", err)
   110  					continue
   111  				}
   112  			}
   113  
   114  			logrus.Errorf("Not continuing with push after error: %v", err)
   115  			return err
   116  		}
   117  
   118  		imagePushConfig.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "push")
   119  		return nil
   120  	}
   121  
   122  	if lastErr == nil {
   123  		lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.Name.Name())
   124  	}
   125  	return lastErr
   126  }
   127  
   128  // compress returns an io.ReadCloser which will supply a compressed version of
   129  // the provided Reader. The caller must close the ReadCloser after reading the
   130  // compressed data.
   131  //
   132  // Note that this function returns a reader instead of taking a writer as an
   133  // argument so that it can be used with httpBlobWriter's ReadFrom method.
   134  // Using httpBlobWriter's Write method would send a PATCH request for every
   135  // Write call.
   136  //
   137  // The second return value is a channel that gets closed when the goroutine
   138  // is finished. This allows the caller to make sure the goroutine finishes
   139  // before it releases any resources connected with the reader that was
   140  // passed in.
   141  func compress(in io.Reader) (io.ReadCloser, chan struct{}) {
   142  	compressionDone := make(chan struct{})
   143  
   144  	pipeReader, pipeWriter := io.Pipe()
   145  	// Use a bufio.Writer to avoid excessive chunking in HTTP request.
   146  	bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize)
   147  	compressor := gzip.NewWriter(bufWriter)
   148  
   149  	go func() {
   150  		_, err := io.Copy(compressor, in)
   151  		if err == nil {
   152  			err = compressor.Close()
   153  		}
   154  		if err == nil {
   155  			err = bufWriter.Flush()
   156  		}
   157  		if err != nil {
   158  			pipeWriter.CloseWithError(err)
   159  		} else {
   160  			pipeWriter.Close()
   161  		}
   162  		close(compressionDone)
   163  	}()
   164  
   165  	return pipeReader, compressionDone
   166  }