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