github.com/rawahars/moby@v24.0.4+incompatible/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/pkg/progress"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  const compressionBufSize = 32768
    16  
    17  // Push initiates a push operation on ref. ref is the specific variant of the
    18  // image to push. If no tag is provided, all tags are pushed.
    19  func Push(ctx context.Context, ref reference.Named, config *ImagePushConfig) error {
    20  	// FIXME: Allow to interrupt current push when new push of same image is done.
    21  
    22  	// Resolve the Repository name from fqn to RepositoryInfo
    23  	repoInfo, err := config.RegistryService.ResolveRepository(ref)
    24  	if err != nil {
    25  		return err
    26  	}
    27  
    28  	endpoints, err := config.RegistryService.LookupPushEndpoints(reference.Domain(repoInfo.Name))
    29  	if err != nil {
    30  		return err
    31  	}
    32  
    33  	progress.Messagef(config.ProgressOutput, "", "The push refers to repository [%s]", repoInfo.Name.Name())
    34  
    35  	associations := config.ReferenceStore.ReferencesByName(repoInfo.Name)
    36  	if len(associations) == 0 {
    37  		return fmt.Errorf("An image does not exist locally with the tag: %s", reference.FamiliarName(repoInfo.Name))
    38  	}
    39  
    40  	var (
    41  		lastErr error
    42  
    43  		// confirmedTLSRegistries is a map indicating which registries
    44  		// are known to be using TLS. There should never be a plaintext
    45  		// retry for any of these.
    46  		confirmedTLSRegistries = make(map[string]struct{})
    47  	)
    48  
    49  	for _, endpoint := range endpoints {
    50  		if endpoint.URL.Scheme != "https" {
    51  			if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS {
    52  				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
    53  				continue
    54  			}
    55  		}
    56  
    57  		logrus.Debugf("Trying to push %s to %s", repoInfo.Name.Name(), endpoint.URL)
    58  
    59  		if err := newPusher(ref, endpoint, repoInfo, config).push(ctx); err != nil {
    60  			// Was this push cancelled? If so, don't try to fall
    61  			// back.
    62  			select {
    63  			case <-ctx.Done():
    64  			default:
    65  				if fallbackErr, ok := err.(fallbackError); ok {
    66  					if fallbackErr.transportOK && endpoint.URL.Scheme == "https" {
    67  						confirmedTLSRegistries[endpoint.URL.Host] = struct{}{}
    68  					}
    69  					err = fallbackErr.err
    70  					lastErr = err
    71  					logrus.Infof("Attempting next endpoint for push after error: %v", err)
    72  					continue
    73  				}
    74  			}
    75  
    76  			logrus.Errorf("Not continuing with push after error: %v", err)
    77  			return err
    78  		}
    79  
    80  		config.ImageEventLogger(reference.FamiliarString(ref), reference.FamiliarName(repoInfo.Name), "push")
    81  		return nil
    82  	}
    83  
    84  	if lastErr == nil {
    85  		lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.Name.Name())
    86  	}
    87  	return lastErr
    88  }
    89  
    90  // compress returns an io.ReadCloser which will supply a compressed version of
    91  // the provided Reader. The caller must close the ReadCloser after reading the
    92  // compressed data.
    93  //
    94  // Note that this function returns a reader instead of taking a writer as an
    95  // argument so that it can be used with httpBlobWriter's ReadFrom method.
    96  // Using httpBlobWriter's Write method would send a PATCH request for every
    97  // Write call.
    98  //
    99  // The second return value is a channel that gets closed when the goroutine
   100  // is finished. This allows the caller to make sure the goroutine finishes
   101  // before it releases any resources connected with the reader that was
   102  // passed in.
   103  func compress(in io.Reader) (io.ReadCloser, chan struct{}) {
   104  	compressionDone := make(chan struct{})
   105  
   106  	pipeReader, pipeWriter := io.Pipe()
   107  	// Use a bufio.Writer to avoid excessive chunking in HTTP request.
   108  	bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize)
   109  	compressor := gzip.NewWriter(bufWriter)
   110  
   111  	go func() {
   112  		_, err := io.Copy(compressor, in)
   113  		if err == nil {
   114  			err = compressor.Close()
   115  		}
   116  		if err == nil {
   117  			err = bufWriter.Flush()
   118  		}
   119  		if err != nil {
   120  			pipeWriter.CloseWithError(err)
   121  		} else {
   122  			pipeWriter.Close()
   123  		}
   124  		close(compressionDone)
   125  	}()
   126  
   127  	return pipeReader, compressionDone
   128  }