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 }