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 }