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