github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/distribution/push.go (about) 1 package distribution 2 3 import ( 4 "bufio" 5 "compress/gzip" 6 "fmt" 7 "io" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/distribution/metadata" 11 "github.com/docker/docker/distribution/xfer" 12 "github.com/docker/docker/image" 13 "github.com/docker/docker/layer" 14 "github.com/docker/docker/pkg/progress" 15 "github.com/docker/docker/reference" 16 "github.com/docker/docker/registry" 17 "github.com/docker/engine-api/types" 18 "github.com/docker/libtrust" 19 "golang.org/x/net/context" 20 ) 21 22 // ImagePushConfig stores push configuration. 23 type ImagePushConfig struct { 24 // MetaHeaders store HTTP headers with metadata about the image 25 MetaHeaders map[string][]string 26 // AuthConfig holds authentication credentials for authenticating with 27 // the registry. 28 AuthConfig *types.AuthConfig 29 // ProgressOutput is the interface for showing the status of the push 30 // operation. 31 ProgressOutput progress.Output 32 // RegistryService is the registry service to use for TLS configuration 33 // and endpoint lookup. 34 RegistryService *registry.Service 35 // ImageEventLogger notifies events for a given image 36 ImageEventLogger func(id, name, action string) 37 // MetadataStore is the storage backend for distribution-specific 38 // metadata. 39 MetadataStore metadata.Store 40 // LayerStore manages layers. 41 LayerStore layer.Store 42 // ImageStore manages images. 43 ImageStore image.Store 44 // ReferenceStore manages tags. 45 ReferenceStore reference.Store 46 // TrustKey is the private key for legacy signatures. This is typically 47 // an ephemeral key, since these signatures are no longer verified. 48 TrustKey libtrust.PrivateKey 49 // UploadManager dispatches uploads. 50 UploadManager *xfer.LayerUploadManager 51 } 52 53 // Pusher is an interface that abstracts pushing for different API versions. 54 type Pusher interface { 55 // Push tries to push the image configured at the creation of Pusher. 56 // Push returns an error if any, as well as a boolean that determines whether to retry Push on the next configured endpoint. 57 // 58 // TODO(tiborvass): have Push() take a reference to repository + tag, so that the pusher itself is repository-agnostic. 59 Push(ctx context.Context) error 60 } 61 62 const compressionBufSize = 32768 63 64 // NewPusher creates a new Pusher interface that will push to either a v1 or v2 65 // registry. The endpoint argument contains a Version field that determines 66 // whether a v1 or v2 pusher will be created. The other parameters are passed 67 // through to the underlying pusher implementation for use during the actual 68 // push operation. 69 func NewPusher(ref reference.Named, endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, imagePushConfig *ImagePushConfig) (Pusher, error) { 70 switch endpoint.Version { 71 case registry.APIVersion2: 72 return &v2Pusher{ 73 v2MetadataService: metadata.NewV2MetadataService(imagePushConfig.MetadataStore), 74 ref: ref, 75 endpoint: endpoint, 76 repoInfo: repoInfo, 77 config: imagePushConfig, 78 }, nil 79 case registry.APIVersion1: 80 return &v1Pusher{ 81 v1IDService: metadata.NewV1IDService(imagePushConfig.MetadataStore), 82 ref: ref, 83 endpoint: endpoint, 84 repoInfo: repoInfo, 85 config: imagePushConfig, 86 }, nil 87 } 88 return nil, fmt.Errorf("unknown version %d for registry %s", endpoint.Version, endpoint.URL) 89 } 90 91 // Push initiates a push operation on the repository named localName. 92 // ref is the specific variant of the image to be pushed. 93 // If no tag is provided, all tags will be pushed. 94 func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushConfig) error { 95 // FIXME: Allow to interrupt current push when new push of same image is done. 96 97 // Resolve the Repository name from fqn to RepositoryInfo 98 repoInfo, err := imagePushConfig.RegistryService.ResolveRepository(ref) 99 if err != nil { 100 return err 101 } 102 103 endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(repoInfo.Hostname()) 104 if err != nil { 105 return err 106 } 107 108 progress.Messagef(imagePushConfig.ProgressOutput, "", "The push refers to a repository [%s]", repoInfo.FullName()) 109 110 associations := imagePushConfig.ReferenceStore.ReferencesByName(repoInfo) 111 if len(associations) == 0 { 112 return fmt.Errorf("An image does not exist locally with the tag: %s", repoInfo.Name()) 113 } 114 115 var ( 116 lastErr error 117 118 // confirmedV2 is set to true if a push attempt managed to 119 // confirm that it was talking to a v2 registry. This will 120 // prevent fallback to the v1 protocol. 121 confirmedV2 bool 122 123 // confirmedTLSRegistries is a map indicating which registries 124 // are known to be using TLS. There should never be a plaintext 125 // retry for any of these. 126 confirmedTLSRegistries = make(map[string]struct{}) 127 ) 128 129 for _, endpoint := range endpoints { 130 if confirmedV2 && endpoint.Version == registry.APIVersion1 { 131 logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL) 132 continue 133 } 134 135 if endpoint.URL.Scheme != "https" { 136 if _, confirmedTLS := confirmedTLSRegistries[endpoint.URL.Host]; confirmedTLS { 137 logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL) 138 continue 139 } 140 } 141 142 logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version) 143 144 pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig) 145 if err != nil { 146 lastErr = err 147 continue 148 } 149 if err := pusher.Push(ctx); err != nil { 150 // Was this push cancelled? If so, don't try to fall 151 // back. 152 select { 153 case <-ctx.Done(): 154 default: 155 if fallbackErr, ok := err.(fallbackError); ok { 156 confirmedV2 = confirmedV2 || fallbackErr.confirmedV2 157 if fallbackErr.transportOK && endpoint.URL.Scheme == "https" { 158 confirmedTLSRegistries[endpoint.URL.Host] = struct{}{} 159 } 160 err = fallbackErr.err 161 lastErr = err 162 logrus.Errorf("Attempting next endpoint for push after error: %v", err) 163 continue 164 } 165 } 166 167 logrus.Errorf("Not continuing with push after error: %v", err) 168 return err 169 } 170 171 imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push") 172 return nil 173 } 174 175 if lastErr == nil { 176 lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.FullName()) 177 } 178 return lastErr 179 } 180 181 // compress returns an io.ReadCloser which will supply a compressed version of 182 // the provided Reader. The caller must close the ReadCloser after reading the 183 // compressed data. 184 // 185 // Note that this function returns a reader instead of taking a writer as an 186 // argument so that it can be used with httpBlobWriter's ReadFrom method. 187 // Using httpBlobWriter's Write method would send a PATCH request for every 188 // Write call. 189 // 190 // The second return value is a channel that gets closed when the goroutine 191 // is finished. This allows the caller to make sure the goroutine finishes 192 // before it releases any resources connected with the reader that was 193 // passed in. 194 func compress(in io.Reader) (io.ReadCloser, chan struct{}) { 195 compressionDone := make(chan struct{}) 196 197 pipeReader, pipeWriter := io.Pipe() 198 // Use a bufio.Writer to avoid excessive chunking in HTTP request. 199 bufWriter := bufio.NewWriterSize(pipeWriter, compressionBufSize) 200 compressor := gzip.NewWriter(bufWriter) 201 202 go func() { 203 _, err := io.Copy(compressor, in) 204 if err == nil { 205 err = compressor.Close() 206 } 207 if err == nil { 208 err = bufWriter.Flush() 209 } 210 if err != nil { 211 pipeWriter.CloseWithError(err) 212 } else { 213 pipeWriter.Close() 214 } 215 close(compressionDone) 216 }() 217 218 return pipeReader, compressionDone 219 }