github.com/uriddle/docker@v0.0.0-20210926094723-4072e6aeb013/distribution/push_v2.go (about) 1 package distribution 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "sync" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/distribution" 11 "github.com/docker/distribution/digest" 12 "github.com/docker/distribution/manifest/schema1" 13 "github.com/docker/distribution/manifest/schema2" 14 distreference "github.com/docker/distribution/reference" 15 "github.com/docker/distribution/registry/client" 16 "github.com/docker/docker/distribution/metadata" 17 "github.com/docker/docker/distribution/xfer" 18 "github.com/docker/docker/image" 19 "github.com/docker/docker/layer" 20 "github.com/docker/docker/pkg/ioutils" 21 "github.com/docker/docker/pkg/progress" 22 "github.com/docker/docker/pkg/stringid" 23 "github.com/docker/docker/reference" 24 "github.com/docker/docker/registry" 25 "golang.org/x/net/context" 26 ) 27 28 // PushResult contains the tag, manifest digest, and manifest size from the 29 // push. It's used to signal this information to the trust code in the client 30 // so it can sign the manifest if necessary. 31 type PushResult struct { 32 Tag string 33 Digest digest.Digest 34 Size int 35 } 36 37 type v2Pusher struct { 38 v2MetadataService *metadata.V2MetadataService 39 ref reference.Named 40 endpoint registry.APIEndpoint 41 repoInfo *registry.RepositoryInfo 42 config *ImagePushConfig 43 repo distribution.Repository 44 45 // pushState is state built by the Download functions. 46 pushState pushState 47 } 48 49 type pushState struct { 50 sync.Mutex 51 // remoteLayers is the set of layers known to exist on the remote side. 52 // This avoids redundant queries when pushing multiple tags that 53 // involve the same layers. It is also used to fill in digest and size 54 // information when building the manifest. 55 remoteLayers map[layer.DiffID]distribution.Descriptor 56 // confirmedV2 is set to true if we confirm we're talking to a v2 57 // registry. This is used to limit fallbacks to the v1 protocol. 58 confirmedV2 bool 59 } 60 61 func (p *v2Pusher) Push(ctx context.Context) (err error) { 62 p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor) 63 64 p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull") 65 if err != nil { 66 logrus.Debugf("Error getting v2 registry: %v", err) 67 return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2} 68 } 69 70 if err = p.pushV2Repository(ctx); err != nil { 71 if registry.ContinueOnError(err) { 72 return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2} 73 } 74 } 75 return err 76 } 77 78 func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) { 79 if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged { 80 imageID, err := p.config.ReferenceStore.Get(p.ref) 81 if err != nil { 82 return fmt.Errorf("tag does not exist: %s", p.ref.String()) 83 } 84 85 return p.pushV2Tag(ctx, namedTagged, imageID) 86 } 87 88 if !reference.IsNameOnly(p.ref) { 89 return errors.New("cannot push a digest reference") 90 } 91 92 // Pull all tags 93 pushed := 0 94 for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) { 95 if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged { 96 pushed++ 97 if err := p.pushV2Tag(ctx, namedTagged, association.ImageID); err != nil { 98 return err 99 } 100 } 101 } 102 103 if pushed == 0 { 104 return fmt.Errorf("no tags to push for %s", p.repoInfo.Name()) 105 } 106 107 return nil 108 } 109 110 func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, imageID image.ID) error { 111 logrus.Debugf("Pushing repository: %s", ref.String()) 112 113 img, err := p.config.ImageStore.Get(imageID) 114 if err != nil { 115 return fmt.Errorf("could not find image from tag %s: %v", ref.String(), err) 116 } 117 118 var l layer.Layer 119 120 topLayerID := img.RootFS.ChainID() 121 if topLayerID == "" { 122 l = layer.EmptyLayer 123 } else { 124 l, err = p.config.LayerStore.Get(topLayerID) 125 if err != nil { 126 return fmt.Errorf("failed to get top layer from image: %v", err) 127 } 128 defer layer.ReleaseAndLog(p.config.LayerStore, l) 129 } 130 131 var descriptors []xfer.UploadDescriptor 132 133 descriptorTemplate := v2PushDescriptor{ 134 v2MetadataService: p.v2MetadataService, 135 repoInfo: p.repoInfo, 136 repo: p.repo, 137 pushState: &p.pushState, 138 } 139 140 // Loop bounds condition is to avoid pushing the base layer on Windows. 141 for i := 0; i < len(img.RootFS.DiffIDs); i++ { 142 descriptor := descriptorTemplate 143 descriptor.layer = l 144 descriptors = append(descriptors, &descriptor) 145 146 l = l.Parent() 147 } 148 149 if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil { 150 return err 151 } 152 153 // Try schema2 first 154 builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), img.RawJSON()) 155 manifest, err := manifestFromBuilder(ctx, builder, descriptors) 156 if err != nil { 157 return err 158 } 159 160 manSvc, err := p.repo.Manifests(ctx) 161 if err != nil { 162 return err 163 } 164 165 putOptions := []distribution.ManifestServiceOption{client.WithTag(ref.Tag())} 166 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 167 logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err) 168 169 builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, p.repo.Name(), ref.Tag(), img.RawJSON()) 170 manifest, err = manifestFromBuilder(ctx, builder, descriptors) 171 if err != nil { 172 return err 173 } 174 175 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 176 return err 177 } 178 } 179 180 var canonicalManifest []byte 181 182 switch v := manifest.(type) { 183 case *schema1.SignedManifest: 184 canonicalManifest = v.Canonical 185 case *schema2.DeserializedManifest: 186 _, canonicalManifest, err = v.Payload() 187 if err != nil { 188 return err 189 } 190 } 191 192 manifestDigest := digest.FromBytes(canonicalManifest) 193 progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest)) 194 // Signal digest to the trust client so it can sign the 195 // push, if appropriate. 196 progress.Aux(p.config.ProgressOutput, PushResult{Tag: ref.Tag(), Digest: manifestDigest, Size: len(canonicalManifest)}) 197 198 return nil 199 } 200 201 func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) { 202 // descriptors is in reverse order; iterate backwards to get references 203 // appended in the right order. 204 for i := len(descriptors) - 1; i >= 0; i-- { 205 if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil { 206 return nil, err 207 } 208 } 209 210 return builder.Build(ctx) 211 } 212 213 type v2PushDescriptor struct { 214 layer layer.Layer 215 v2MetadataService *metadata.V2MetadataService 216 repoInfo reference.Named 217 repo distribution.Repository 218 pushState *pushState 219 } 220 221 func (pd *v2PushDescriptor) Key() string { 222 return "v2push:" + pd.repo.Name() + " " + pd.layer.DiffID().String() 223 } 224 225 func (pd *v2PushDescriptor) ID() string { 226 return stringid.TruncateID(pd.layer.DiffID().String()) 227 } 228 229 func (pd *v2PushDescriptor) DiffID() layer.DiffID { 230 return pd.layer.DiffID() 231 } 232 233 func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) error { 234 diffID := pd.DiffID() 235 236 pd.pushState.Lock() 237 if _, ok := pd.pushState.remoteLayers[diffID]; ok { 238 // it is already known that the push is not needed and 239 // therefore doing a stat is unnecessary 240 pd.pushState.Unlock() 241 progress.Update(progressOutput, pd.ID(), "Layer already exists") 242 return nil 243 } 244 pd.pushState.Unlock() 245 246 // Do we have any metadata associated with this layer's DiffID? 247 v2Metadata, err := pd.v2MetadataService.GetMetadata(diffID) 248 if err == nil { 249 descriptor, exists, err := layerAlreadyExists(ctx, v2Metadata, pd.repoInfo, pd.repo, pd.pushState) 250 if err != nil { 251 progress.Update(progressOutput, pd.ID(), "Image push failed") 252 return retryOnError(err) 253 } 254 if exists { 255 progress.Update(progressOutput, pd.ID(), "Layer already exists") 256 pd.pushState.Lock() 257 pd.pushState.remoteLayers[diffID] = descriptor 258 pd.pushState.Unlock() 259 return nil 260 } 261 } 262 263 logrus.Debugf("Pushing layer: %s", diffID) 264 265 // if digest was empty or not saved, or if blob does not exist on the remote repository, 266 // then push the blob. 267 bs := pd.repo.Blobs(ctx) 268 269 var mountFrom metadata.V2Metadata 270 271 // Attempt to find another repository in the same registry to mount the layer from to avoid an unnecessary upload 272 for _, metadata := range v2Metadata { 273 sourceRepo, err := reference.ParseNamed(metadata.SourceRepository) 274 if err != nil { 275 continue 276 } 277 if pd.repoInfo.Hostname() == sourceRepo.Hostname() { 278 logrus.Debugf("attempting to mount layer %s (%s) from %s", diffID, metadata.Digest, sourceRepo.FullName()) 279 mountFrom = metadata 280 break 281 } 282 } 283 284 var createOpts []distribution.BlobCreateOption 285 286 if mountFrom.SourceRepository != "" { 287 namedRef, err := reference.WithName(mountFrom.SourceRepository) 288 if err != nil { 289 return err 290 } 291 292 // TODO (brianbland): We need to construct a reference where the Name is 293 // only the full remote name, so clean this up when distribution has a 294 // richer reference package 295 remoteRef, err := distreference.WithName(namedRef.RemoteName()) 296 if err != nil { 297 return err 298 } 299 300 canonicalRef, err := distreference.WithDigest(remoteRef, mountFrom.Digest) 301 if err != nil { 302 return err 303 } 304 305 createOpts = append(createOpts, client.WithMountFrom(canonicalRef)) 306 } 307 308 // Send the layer 309 layerUpload, err := bs.Create(ctx, createOpts...) 310 switch err := err.(type) { 311 case distribution.ErrBlobMounted: 312 progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name()) 313 314 err.Descriptor.MediaType = schema2.MediaTypeLayer 315 316 pd.pushState.Lock() 317 pd.pushState.confirmedV2 = true 318 pd.pushState.remoteLayers[diffID] = err.Descriptor 319 pd.pushState.Unlock() 320 321 // Cache mapping from this layer's DiffID to the blobsum 322 if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: mountFrom.Digest, SourceRepository: pd.repoInfo.FullName()}); err != nil { 323 return xfer.DoNotRetry{Err: err} 324 } 325 326 return nil 327 } 328 if mountFrom.SourceRepository != "" { 329 // unable to mount layer from this repository, so this source mapping is no longer valid 330 logrus.Debugf("unassociating layer %s (%s) with %s", diffID, mountFrom.Digest, mountFrom.SourceRepository) 331 pd.v2MetadataService.Remove(mountFrom) 332 } 333 334 if err != nil { 335 return retryOnError(err) 336 } 337 defer layerUpload.Close() 338 339 arch, err := pd.layer.TarStream() 340 if err != nil { 341 return xfer.DoNotRetry{Err: err} 342 } 343 344 // don't care if this fails; best effort 345 size, _ := pd.layer.DiffSize() 346 347 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), progressOutput, size, pd.ID(), "Pushing") 348 compressedReader, compressionDone := compress(reader) 349 defer func() { 350 reader.Close() 351 <-compressionDone 352 }() 353 354 digester := digest.Canonical.New() 355 tee := io.TeeReader(compressedReader, digester.Hash()) 356 357 nn, err := layerUpload.ReadFrom(tee) 358 compressedReader.Close() 359 if err != nil { 360 return retryOnError(err) 361 } 362 363 pushDigest := digester.Digest() 364 if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil { 365 return retryOnError(err) 366 } 367 368 logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn) 369 progress.Update(progressOutput, pd.ID(), "Pushed") 370 371 // Cache mapping from this layer's DiffID to the blobsum 372 if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: pushDigest, SourceRepository: pd.repoInfo.FullName()}); err != nil { 373 return xfer.DoNotRetry{Err: err} 374 } 375 376 pd.pushState.Lock() 377 378 // If Commit succeded, that's an indication that the remote registry 379 // speaks the v2 protocol. 380 pd.pushState.confirmedV2 = true 381 382 pd.pushState.remoteLayers[diffID] = distribution.Descriptor{ 383 Digest: pushDigest, 384 MediaType: schema2.MediaTypeLayer, 385 Size: nn, 386 } 387 388 pd.pushState.Unlock() 389 390 return nil 391 } 392 393 func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor { 394 // Not necessary to lock pushStatus because this is always 395 // called after all the mutation in pushStatus. 396 // By the time this function is called, every layer will have 397 // an entry in remoteLayers. 398 return pd.pushState.remoteLayers[pd.DiffID()] 399 } 400 401 // layerAlreadyExists checks if the registry already know about any of the 402 // metadata passed in the "metadata" slice. If it finds one that the registry 403 // knows about, it returns the known digest and "true". 404 func layerAlreadyExists(ctx context.Context, metadata []metadata.V2Metadata, repoInfo reference.Named, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) { 405 for _, meta := range metadata { 406 // Only check blobsums that are known to this repository or have an unknown source 407 if meta.SourceRepository != "" && meta.SourceRepository != repoInfo.FullName() { 408 continue 409 } 410 descriptor, err := repo.Blobs(ctx).Stat(ctx, meta.Digest) 411 switch err { 412 case nil: 413 descriptor.MediaType = schema2.MediaTypeLayer 414 return descriptor, true, nil 415 case distribution.ErrBlobUnknown: 416 // nop 417 default: 418 return distribution.Descriptor{}, false, err 419 } 420 } 421 return distribution.Descriptor{}, false, nil 422 }