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