github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 Upload 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 err 68 } 69 70 if err = p.pushV2Repository(ctx); err != nil { 71 if continueOnError(err) { 72 return fallbackError{ 73 err: err, 74 confirmedV2: p.pushState.confirmedV2, 75 transportOK: true, 76 } 77 } 78 } 79 return err 80 } 81 82 func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) { 83 if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged { 84 imageID, err := p.config.ReferenceStore.Get(p.ref) 85 if err != nil { 86 return fmt.Errorf("tag does not exist: %s", p.ref.String()) 87 } 88 89 return p.pushV2Tag(ctx, namedTagged, imageID) 90 } 91 92 if !reference.IsNameOnly(p.ref) { 93 return errors.New("cannot push a digest reference") 94 } 95 96 // Pull all tags 97 pushed := 0 98 for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) { 99 if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged { 100 pushed++ 101 if err := p.pushV2Tag(ctx, namedTagged, association.ImageID); err != nil { 102 return err 103 } 104 } 105 } 106 107 if pushed == 0 { 108 return fmt.Errorf("no tags to push for %s", p.repoInfo.Name()) 109 } 110 111 return nil 112 } 113 114 func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, imageID image.ID) error { 115 logrus.Debugf("Pushing repository: %s", ref.String()) 116 117 img, err := p.config.ImageStore.Get(imageID) 118 if err != nil { 119 return fmt.Errorf("could not find image from tag %s: %v", ref.String(), err) 120 } 121 122 var l layer.Layer 123 124 topLayerID := img.RootFS.ChainID() 125 if topLayerID == "" { 126 l = layer.EmptyLayer 127 } else { 128 l, err = p.config.LayerStore.Get(topLayerID) 129 if err != nil { 130 return fmt.Errorf("failed to get top layer from image: %v", err) 131 } 132 defer layer.ReleaseAndLog(p.config.LayerStore, l) 133 } 134 135 var descriptors []xfer.UploadDescriptor 136 137 descriptorTemplate := v2PushDescriptor{ 138 v2MetadataService: p.v2MetadataService, 139 repoInfo: p.repoInfo, 140 repo: p.repo, 141 pushState: &p.pushState, 142 } 143 144 // Loop bounds condition is to avoid pushing the base layer on Windows. 145 for i := 0; i < len(img.RootFS.DiffIDs); i++ { 146 descriptor := descriptorTemplate 147 descriptor.layer = l 148 descriptors = append(descriptors, &descriptor) 149 150 l = l.Parent() 151 } 152 153 if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil { 154 return err 155 } 156 157 // Try schema2 first 158 builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), img.RawJSON()) 159 manifest, err := manifestFromBuilder(ctx, builder, descriptors) 160 if err != nil { 161 return err 162 } 163 164 manSvc, err := p.repo.Manifests(ctx) 165 if err != nil { 166 return err 167 } 168 169 putOptions := []distribution.ManifestServiceOption{distribution.WithTag(ref.Tag())} 170 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 171 logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err) 172 173 manifestRef, err := distreference.WithTag(p.repo.Named(), ref.Tag()) 174 if err != nil { 175 return err 176 } 177 builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, manifestRef, img.RawJSON()) 178 manifest, err = manifestFromBuilder(ctx, builder, descriptors) 179 if err != nil { 180 return err 181 } 182 183 if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil { 184 return err 185 } 186 } 187 188 var canonicalManifest []byte 189 190 switch v := manifest.(type) { 191 case *schema1.SignedManifest: 192 canonicalManifest = v.Canonical 193 case *schema2.DeserializedManifest: 194 _, canonicalManifest, err = v.Payload() 195 if err != nil { 196 return err 197 } 198 } 199 200 manifestDigest := digest.FromBytes(canonicalManifest) 201 progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest)) 202 // Signal digest to the trust client so it can sign the 203 // push, if appropriate. 204 progress.Aux(p.config.ProgressOutput, PushResult{Tag: ref.Tag(), Digest: manifestDigest, Size: len(canonicalManifest)}) 205 206 return nil 207 } 208 209 func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) { 210 // descriptors is in reverse order; iterate backwards to get references 211 // appended in the right order. 212 for i := len(descriptors) - 1; i >= 0; i-- { 213 if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil { 214 return nil, err 215 } 216 } 217 218 return builder.Build(ctx) 219 } 220 221 type v2PushDescriptor struct { 222 layer layer.Layer 223 v2MetadataService *metadata.V2MetadataService 224 repoInfo reference.Named 225 repo distribution.Repository 226 pushState *pushState 227 remoteDescriptor distribution.Descriptor 228 } 229 230 func (pd *v2PushDescriptor) Key() string { 231 return "v2push:" + pd.repo.Named().Name() + " " + pd.layer.DiffID().String() 232 } 233 234 func (pd *v2PushDescriptor) ID() string { 235 return stringid.TruncateID(pd.layer.DiffID().String()) 236 } 237 238 func (pd *v2PushDescriptor) DiffID() layer.DiffID { 239 return pd.layer.DiffID() 240 } 241 242 func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (distribution.Descriptor, error) { 243 diffID := pd.DiffID() 244 245 pd.pushState.Lock() 246 if descriptor, ok := pd.pushState.remoteLayers[diffID]; ok { 247 // it is already known that the push is not needed and 248 // therefore doing a stat is unnecessary 249 pd.pushState.Unlock() 250 progress.Update(progressOutput, pd.ID(), "Layer already exists") 251 return descriptor, nil 252 } 253 pd.pushState.Unlock() 254 255 // Do we have any metadata associated with this layer's DiffID? 256 v2Metadata, err := pd.v2MetadataService.GetMetadata(diffID) 257 if err == nil { 258 descriptor, exists, err := layerAlreadyExists(ctx, v2Metadata, pd.repoInfo, pd.repo, pd.pushState) 259 if err != nil { 260 progress.Update(progressOutput, pd.ID(), "Image push failed") 261 return distribution.Descriptor{}, retryOnError(err) 262 } 263 if exists { 264 progress.Update(progressOutput, pd.ID(), "Layer already exists") 265 pd.pushState.Lock() 266 pd.pushState.remoteLayers[diffID] = descriptor 267 pd.pushState.Unlock() 268 return descriptor, nil 269 } 270 } 271 272 logrus.Debugf("Pushing layer: %s", diffID) 273 274 // if digest was empty or not saved, or if blob does not exist on the remote repository, 275 // then push the blob. 276 bs := pd.repo.Blobs(ctx) 277 278 var layerUpload distribution.BlobWriter 279 mountAttemptsRemaining := 3 280 281 // Attempt to find another repository in the same registry to mount the layer 282 // from to avoid an unnecessary upload. 283 // Note: metadata is stored from oldest to newest, so we iterate through this 284 // slice in reverse to maximize our chances of the blob still existing in the 285 // remote repository. 286 for i := len(v2Metadata) - 1; i >= 0 && mountAttemptsRemaining > 0; i-- { 287 mountFrom := v2Metadata[i] 288 289 sourceRepo, err := reference.ParseNamed(mountFrom.SourceRepository) 290 if err != nil { 291 continue 292 } 293 if pd.repoInfo.Hostname() != sourceRepo.Hostname() { 294 // don't mount blobs from another registry 295 continue 296 } 297 298 namedRef, err := reference.WithName(mountFrom.SourceRepository) 299 if err != nil { 300 continue 301 } 302 303 // TODO (brianbland): We need to construct a reference where the Name is 304 // only the full remote name, so clean this up when distribution has a 305 // richer reference package 306 remoteRef, err := distreference.WithName(namedRef.RemoteName()) 307 if err != nil { 308 continue 309 } 310 311 canonicalRef, err := distreference.WithDigest(remoteRef, mountFrom.Digest) 312 if err != nil { 313 continue 314 } 315 316 logrus.Debugf("attempting to mount layer %s (%s) from %s", diffID, mountFrom.Digest, sourceRepo.FullName()) 317 318 layerUpload, err = bs.Create(ctx, client.WithMountFrom(canonicalRef)) 319 switch err := err.(type) { 320 case distribution.ErrBlobMounted: 321 progress.Updatef(progressOutput, pd.ID(), "Mounted from %s", err.From.Name()) 322 323 err.Descriptor.MediaType = schema2.MediaTypeLayer 324 325 pd.pushState.Lock() 326 pd.pushState.confirmedV2 = true 327 pd.pushState.remoteLayers[diffID] = err.Descriptor 328 pd.pushState.Unlock() 329 330 // Cache mapping from this layer's DiffID to the blobsum 331 if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: mountFrom.Digest, SourceRepository: pd.repoInfo.FullName()}); err != nil { 332 return distribution.Descriptor{}, xfer.DoNotRetry{Err: err} 333 } 334 return err.Descriptor, nil 335 case nil: 336 // blob upload session created successfully, so begin the upload 337 mountAttemptsRemaining = 0 338 default: 339 // unable to mount layer from this repository, so this source mapping is no longer valid 340 logrus.Debugf("unassociating layer %s (%s) with %s", diffID, mountFrom.Digest, mountFrom.SourceRepository) 341 pd.v2MetadataService.Remove(mountFrom) 342 mountAttemptsRemaining-- 343 } 344 } 345 346 if layerUpload == nil { 347 layerUpload, err = bs.Create(ctx) 348 if err != nil { 349 return distribution.Descriptor{}, retryOnError(err) 350 } 351 } 352 defer layerUpload.Close() 353 354 arch, err := pd.layer.TarStream() 355 if err != nil { 356 return distribution.Descriptor{}, xfer.DoNotRetry{Err: err} 357 } 358 359 // don't care if this fails; best effort 360 size, _ := pd.layer.DiffSize() 361 362 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, arch), progressOutput, size, pd.ID(), "Pushing") 363 compressedReader, compressionDone := compress(reader) 364 defer func() { 365 reader.Close() 366 <-compressionDone 367 }() 368 369 digester := digest.Canonical.New() 370 tee := io.TeeReader(compressedReader, digester.Hash()) 371 372 nn, err := layerUpload.ReadFrom(tee) 373 compressedReader.Close() 374 if err != nil { 375 return distribution.Descriptor{}, retryOnError(err) 376 } 377 378 pushDigest := digester.Digest() 379 if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil { 380 return distribution.Descriptor{}, retryOnError(err) 381 } 382 383 logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn) 384 progress.Update(progressOutput, pd.ID(), "Pushed") 385 386 // Cache mapping from this layer's DiffID to the blobsum 387 if err := pd.v2MetadataService.Add(diffID, metadata.V2Metadata{Digest: pushDigest, SourceRepository: pd.repoInfo.FullName()}); err != nil { 388 return distribution.Descriptor{}, xfer.DoNotRetry{Err: err} 389 } 390 391 pd.pushState.Lock() 392 393 // If Commit succeeded, that's an indication that the remote registry 394 // speaks the v2 protocol. 395 pd.pushState.confirmedV2 = true 396 397 descriptor := distribution.Descriptor{ 398 Digest: pushDigest, 399 MediaType: schema2.MediaTypeLayer, 400 Size: nn, 401 } 402 pd.pushState.remoteLayers[diffID] = descriptor 403 404 pd.pushState.Unlock() 405 406 return descriptor, nil 407 } 408 409 func (pd *v2PushDescriptor) SetRemoteDescriptor(descriptor distribution.Descriptor) { 410 pd.remoteDescriptor = descriptor 411 } 412 413 func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor { 414 return pd.remoteDescriptor 415 } 416 417 // layerAlreadyExists checks if the registry already know about any of the 418 // metadata passed in the "metadata" slice. If it finds one that the registry 419 // knows about, it returns the known digest and "true". 420 func layerAlreadyExists(ctx context.Context, metadata []metadata.V2Metadata, repoInfo reference.Named, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) { 421 for _, meta := range metadata { 422 // Only check blobsums that are known to this repository or have an unknown source 423 if meta.SourceRepository != "" && meta.SourceRepository != repoInfo.FullName() { 424 continue 425 } 426 descriptor, err := repo.Blobs(ctx).Stat(ctx, meta.Digest) 427 switch err { 428 case nil: 429 descriptor.MediaType = schema2.MediaTypeLayer 430 return descriptor, true, nil 431 case distribution.ErrBlobUnknown: 432 // nop 433 default: 434 return distribution.Descriptor{}, false, err 435 } 436 } 437 return distribution.Descriptor{}, false, nil 438 }