Browse code

Merge pull request #18785 from aaronlehmann/new-manifest

New image manifest format

Arnaud Porterie authored on 2016/01/12 05:01:46
Showing 62 changed files
... ...
@@ -147,14 +147,21 @@ RUN set -x \
147 147
 	) \
148 148
 	&& rm -rf "$SECCOMP_PATH"
149 149
 
150
-# Install registry
151
-ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
150
+# Install two versions of the registry. The first is an older version that
151
+# only supports schema1 manifests. The second is a newer version that supports
152
+# both. This allows integration-cli tests to cover push/pull with both schema1
153
+# and schema2 manifests.
154
+ENV REGISTRY_COMMIT_SCHEMA1 ec87e9b6971d831f0eff752ddb54fb64693e51cd
155
+ENV REGISTRY_COMMIT a7ae88da459b98b481a245e5b1750134724ac67d
152 156
 RUN set -x \
153 157
 	&& export GOPATH="$(mktemp -d)" \
154 158
 	&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
155 159
 	&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT") \
156 160
 	&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
157 161
 		go build -o /usr/local/bin/registry-v2 github.com/docker/distribution/cmd/registry \
162
+	&& (cd "$GOPATH/src/github.com/docker/distribution" && git checkout -q "$REGISTRY_COMMIT_SCHEMA1") \
163
+	&& GOPATH="$GOPATH/src/github.com/docker/distribution/Godeps/_workspace:$GOPATH" \
164
+		go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
158 165
 	&& rm -rf "$GOPATH"
159 166
 
160 167
 # Install notary server
... ...
@@ -12,8 +12,11 @@ import (
12 12
 	"github.com/Sirupsen/logrus"
13 13
 	"github.com/docker/distribution"
14 14
 	"github.com/docker/distribution/digest"
15
+	"github.com/docker/distribution/manifest/manifestlist"
15 16
 	"github.com/docker/distribution/manifest/schema1"
17
+	"github.com/docker/distribution/manifest/schema2"
16 18
 	"github.com/docker/distribution/registry/api/errcode"
19
+	"github.com/docker/distribution/registry/client"
17 20
 	"github.com/docker/docker/distribution/metadata"
18 21
 	"github.com/docker/docker/distribution/xfer"
19 22
 	"github.com/docker/docker/image"
... ...
@@ -27,6 +30,8 @@ import (
27 27
 	"golang.org/x/net/context"
28 28
 )
29 29
 
30
+var errRootFSMismatch = errors.New("layers from manifest don't match image configuration")
31
+
30 32
 type v2Puller struct {
31 33
 	blobSumService *metadata.BlobSumService
32 34
 	endpoint       registry.APIEndpoint
... ...
@@ -61,18 +66,12 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
61 61
 func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
62 62
 	var layersDownloaded bool
63 63
 	if !reference.IsNameOnly(ref) {
64
-		var err error
65 64
 		layersDownloaded, err = p.pullV2Tag(ctx, ref)
66 65
 		if err != nil {
67 66
 			return err
68 67
 		}
69 68
 	} else {
70
-		manSvc, err := p.repo.Manifests(ctx)
71
-		if err != nil {
72
-			return err
73
-		}
74
-
75
-		tags, err := manSvc.Tags()
69
+		tags, err := p.repo.Tags(ctx).All(ctx)
76 70
 		if err != nil {
77 71
 			// If this repository doesn't exist on V2, we should
78 72
 			// permit a fallback to V1.
... ...
@@ -84,8 +83,6 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
84 84
 		// error later on.
85 85
 		p.confirmedV2 = true
86 86
 
87
-		// This probably becomes a lot nicer after the manifest
88
-		// refactor...
89 87
 		for _, tag := range tags {
90 88
 			tagRef, err := reference.WithTag(ref, tag)
91 89
 			if err != nil {
... ...
@@ -203,58 +200,111 @@ func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
203 203
 }
204 204
 
205 205
 func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) {
206
-	tagOrDigest := ""
206
+	manSvc, err := p.repo.Manifests(ctx)
207
+	if err != nil {
208
+		return false, err
209
+	}
210
+
211
+	var (
212
+		manifest    distribution.Manifest
213
+		tagOrDigest string // Used for logging/progress only
214
+	)
207 215
 	if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
216
+		// NOTE: not using TagService.Get, since it uses HEAD requests
217
+		// against the manifests endpoint, which are not supported by
218
+		// all registry versions.
219
+		manifest, err = manSvc.Get(ctx, "", client.WithTag(tagged.Tag()))
220
+		if err != nil {
221
+			return false, allowV1Fallback(err)
222
+		}
208 223
 		tagOrDigest = tagged.Tag()
209
-	} else if digested, isCanonical := ref.(reference.Canonical); isCanonical {
224
+	} else if digested, isDigested := ref.(reference.Canonical); isDigested {
225
+		manifest, err = manSvc.Get(ctx, digested.Digest())
226
+		if err != nil {
227
+			return false, err
228
+		}
210 229
 		tagOrDigest = digested.Digest().String()
211 230
 	} else {
212 231
 		return false, fmt.Errorf("internal error: reference has neither a tag nor a digest: %s", ref.String())
213 232
 	}
214 233
 
215
-	logrus.Debugf("Pulling ref from V2 registry: %s:%s", ref.FullName(), tagOrDigest)
234
+	if manifest == nil {
235
+		return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
236
+	}
216 237
 
217
-	manSvc, err := p.repo.Manifests(ctx)
218
-	if err != nil {
219
-		return false, err
238
+	// If manSvc.Get succeeded, we can be confident that the registry on
239
+	// the other side speaks the v2 protocol.
240
+	p.confirmedV2 = true
241
+
242
+	logrus.Debugf("Pulling ref from V2 registry: %s", ref.String())
243
+	progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Name())
244
+
245
+	var (
246
+		imageID        image.ID
247
+		manifestDigest digest.Digest
248
+	)
249
+
250
+	switch v := manifest.(type) {
251
+	case *schema1.SignedManifest:
252
+		imageID, manifestDigest, err = p.pullSchema1(ctx, ref, v)
253
+		if err != nil {
254
+			return false, err
255
+		}
256
+	case *schema2.DeserializedManifest:
257
+		imageID, manifestDigest, err = p.pullSchema2(ctx, ref, v)
258
+		if err != nil {
259
+			return false, err
260
+		}
261
+	case *manifestlist.DeserializedManifestList:
262
+		imageID, manifestDigest, err = p.pullManifestList(ctx, ref, v)
263
+		if err != nil {
264
+			return false, err
265
+		}
266
+	default:
267
+		return false, errors.New("unsupported manifest format")
220 268
 	}
221 269
 
222
-	unverifiedManifest, err := manSvc.GetByTag(tagOrDigest)
223
-	if err != nil {
224
-		// If this manifest did not exist, we should allow a possible
225
-		// fallback to the v1 protocol, because dual-version setups may
226
-		// not host all manifests with the v2 protocol. We may also get
227
-		// a "not authorized" error if the manifest doesn't exist.
228
-		return false, allowV1Fallback(err)
270
+	progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String())
271
+
272
+	oldTagImageID, err := p.config.ReferenceStore.Get(ref)
273
+	if err == nil {
274
+		if oldTagImageID == imageID {
275
+			return false, nil
276
+		}
277
+	} else if err != reference.ErrDoesNotExist {
278
+		return false, err
229 279
 	}
230
-	if unverifiedManifest == nil {
231
-		return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest)
280
+
281
+	if canonical, ok := ref.(reference.Canonical); ok {
282
+		if err = p.config.ReferenceStore.AddDigest(canonical, imageID, true); err != nil {
283
+			return false, err
284
+		}
285
+	} else if err = p.config.ReferenceStore.AddTag(ref, imageID, true); err != nil {
286
+		return false, err
232 287
 	}
233 288
 
234
-	// If GetByTag succeeded, we can be confident that the registry on
235
-	// the other side speaks the v2 protocol.
236
-	p.confirmedV2 = true
289
+	return true, nil
290
+}
237 291
 
292
+func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverifiedManifest *schema1.SignedManifest) (imageID image.ID, manifestDigest digest.Digest, err error) {
238 293
 	var verifiedManifest *schema1.Manifest
239
-	verifiedManifest, err = verifyManifest(unverifiedManifest, ref)
294
+	verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
240 295
 	if err != nil {
241
-		return false, err
296
+		return "", "", err
242 297
 	}
243 298
 
244 299
 	rootFS := image.NewRootFS()
245 300
 
246 301
 	if err := detectBaseLayer(p.config.ImageStore, verifiedManifest, rootFS); err != nil {
247
-		return false, err
302
+		return "", "", err
248 303
 	}
249 304
 
250 305
 	// remove duplicate layers and check parent chain validity
251 306
 	err = fixManifestLayers(verifiedManifest)
252 307
 	if err != nil {
253
-		return false, err
308
+		return "", "", err
254 309
 	}
255 310
 
256
-	progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Name())
257
-
258 311
 	var descriptors []xfer.DownloadDescriptor
259 312
 
260 313
 	// Image history converted to the new format
... ...
@@ -269,12 +319,12 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
269 269
 			ThrowAway bool `json:"throwaway,omitempty"`
270 270
 		}
271 271
 		if err := json.Unmarshal([]byte(verifiedManifest.History[i].V1Compatibility), &throwAway); err != nil {
272
-			return false, err
272
+			return "", "", err
273 273
 		}
274 274
 
275 275
 		h, err := v1.HistoryFromConfig([]byte(verifiedManifest.History[i].V1Compatibility), throwAway.ThrowAway)
276 276
 		if err != nil {
277
-			return false, err
277
+			return "", "", err
278 278
 		}
279 279
 		history = append(history, h)
280 280
 
... ...
@@ -293,43 +343,257 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
293 293
 
294 294
 	resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput)
295 295
 	if err != nil {
296
-		return false, err
296
+		return "", "", err
297 297
 	}
298 298
 	defer release()
299 299
 
300 300
 	config, err := v1.MakeConfigFromV1Config([]byte(verifiedManifest.History[0].V1Compatibility), &resultRootFS, history)
301 301
 	if err != nil {
302
-		return false, err
302
+		return "", "", err
303 303
 	}
304 304
 
305
-	imageID, err := p.config.ImageStore.Create(config)
305
+	imageID, err = p.config.ImageStore.Create(config)
306 306
 	if err != nil {
307
-		return false, err
307
+		return "", "", err
308 308
 	}
309 309
 
310
-	manifestDigest, _, err := digestFromManifest(unverifiedManifest, p.repoInfo)
310
+	manifestDigest = digest.FromBytes(unverifiedManifest.Canonical)
311
+
312
+	return imageID, manifestDigest, nil
313
+}
314
+
315
+func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest) (imageID image.ID, manifestDigest digest.Digest, err error) {
316
+	manifestDigest, err = schema2ManifestDigest(ref, mfst)
311 317
 	if err != nil {
312
-		return false, err
318
+		return "", "", err
313 319
 	}
314 320
 
315
-	if manifestDigest != "" {
316
-		progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String())
321
+	target := mfst.Target()
322
+	imageID = image.ID(target.Digest)
323
+	if _, err := p.config.ImageStore.Get(imageID); err == nil {
324
+		// If the image already exists locally, no need to pull
325
+		// anything.
326
+		return imageID, manifestDigest, nil
317 327
 	}
318 328
 
319
-	oldTagImageID, err := p.config.ReferenceStore.Get(ref)
320
-	if err == nil && oldTagImageID == imageID {
321
-		return false, nil
329
+	configChan := make(chan []byte, 1)
330
+	errChan := make(chan error, 1)
331
+	var cancel func()
332
+	ctx, cancel = context.WithCancel(ctx)
333
+
334
+	// Pull the image config
335
+	go func() {
336
+		configJSON, err := p.pullSchema2ImageConfig(ctx, target.Digest)
337
+		if err != nil {
338
+			errChan <- err
339
+			cancel()
340
+			return
341
+		}
342
+		configChan <- configJSON
343
+	}()
344
+
345
+	var descriptors []xfer.DownloadDescriptor
346
+
347
+	// Note that the order of this loop is in the direction of bottom-most
348
+	// to top-most, so that the downloads slice gets ordered correctly.
349
+	for _, d := range mfst.References() {
350
+		layerDescriptor := &v2LayerDescriptor{
351
+			digest:         d.Digest,
352
+			repo:           p.repo,
353
+			blobSumService: p.blobSumService,
354
+		}
355
+
356
+		descriptors = append(descriptors, layerDescriptor)
322 357
 	}
323 358
 
324
-	if canonical, ok := ref.(reference.Canonical); ok {
325
-		if err = p.config.ReferenceStore.AddDigest(canonical, imageID, true); err != nil {
326
-			return false, err
359
+	var (
360
+		configJSON         []byte       // raw serialized image config
361
+		unmarshalledConfig image.Image  // deserialized image config
362
+		downloadRootFS     image.RootFS // rootFS to use for registering layers.
363
+	)
364
+	if runtime.GOOS == "windows" {
365
+		configJSON, unmarshalledConfig, err = receiveConfig(configChan, errChan)
366
+		if err != nil {
367
+			return "", "", err
327 368
 		}
328
-	} else if err = p.config.ReferenceStore.AddTag(ref, imageID, true); err != nil {
329
-		return false, err
369
+		if unmarshalledConfig.RootFS == nil {
370
+			return "", "", errors.New("image config has no rootfs section")
371
+		}
372
+		downloadRootFS = *unmarshalledConfig.RootFS
373
+		downloadRootFS.DiffIDs = []layer.DiffID{}
374
+	} else {
375
+		downloadRootFS = *image.NewRootFS()
330 376
 	}
331 377
 
332
-	return true, nil
378
+	rootFS, release, err := p.config.DownloadManager.Download(ctx, downloadRootFS, descriptors, p.config.ProgressOutput)
379
+	if err != nil {
380
+		if configJSON != nil {
381
+			// Already received the config
382
+			return "", "", err
383
+		}
384
+		select {
385
+		case err = <-errChan:
386
+			return "", "", err
387
+		default:
388
+			cancel()
389
+			select {
390
+			case <-configChan:
391
+			case <-errChan:
392
+			}
393
+			return "", "", err
394
+		}
395
+	}
396
+	defer release()
397
+
398
+	if configJSON == nil {
399
+		configJSON, unmarshalledConfig, err = receiveConfig(configChan, errChan)
400
+		if err != nil {
401
+			return "", "", err
402
+		}
403
+	}
404
+
405
+	// The DiffIDs returned in rootFS MUST match those in the config.
406
+	// Otherwise the image config could be referencing layers that aren't
407
+	// included in the manifest.
408
+	if len(rootFS.DiffIDs) != len(unmarshalledConfig.RootFS.DiffIDs) {
409
+		return "", "", errRootFSMismatch
410
+	}
411
+
412
+	for i := range rootFS.DiffIDs {
413
+		if rootFS.DiffIDs[i] != unmarshalledConfig.RootFS.DiffIDs[i] {
414
+			return "", "", errRootFSMismatch
415
+		}
416
+	}
417
+
418
+	imageID, err = p.config.ImageStore.Create(configJSON)
419
+	if err != nil {
420
+		return "", "", err
421
+	}
422
+
423
+	return imageID, manifestDigest, nil
424
+}
425
+
426
+func receiveConfig(configChan <-chan []byte, errChan <-chan error) ([]byte, image.Image, error) {
427
+	select {
428
+	case configJSON := <-configChan:
429
+		var unmarshalledConfig image.Image
430
+		if err := json.Unmarshal(configJSON, &unmarshalledConfig); err != nil {
431
+			return nil, image.Image{}, err
432
+		}
433
+		return configJSON, unmarshalledConfig, nil
434
+	case err := <-errChan:
435
+		return nil, image.Image{}, err
436
+		// Don't need a case for ctx.Done in the select because cancellation
437
+		// will trigger an error in p.pullSchema2ImageConfig.
438
+	}
439
+}
440
+
441
+// pullManifestList handles "manifest lists" which point to various
442
+// platform-specifc manifests.
443
+func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (imageID image.ID, manifestListDigest digest.Digest, err error) {
444
+	manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
445
+	if err != nil {
446
+		return "", "", err
447
+	}
448
+
449
+	var manifestDigest digest.Digest
450
+	for _, manifestDescriptor := range mfstList.Manifests {
451
+		// TODO(aaronl): The manifest list spec supports optional
452
+		// "features" and "variant" fields. These are not yet used.
453
+		// Once they are, their values should be interpreted here.
454
+		if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS {
455
+			manifestDigest = manifestDescriptor.Digest
456
+			break
457
+		}
458
+	}
459
+
460
+	if manifestDigest == "" {
461
+		return "", "", errors.New("no supported platform found in manifest list")
462
+	}
463
+
464
+	manSvc, err := p.repo.Manifests(ctx)
465
+	if err != nil {
466
+		return "", "", err
467
+	}
468
+
469
+	manifest, err := manSvc.Get(ctx, manifestDigest)
470
+	if err != nil {
471
+		return "", "", err
472
+	}
473
+
474
+	manifestRef, err := reference.WithDigest(ref, manifestDigest)
475
+	if err != nil {
476
+		return "", "", err
477
+	}
478
+
479
+	switch v := manifest.(type) {
480
+	case *schema1.SignedManifest:
481
+		imageID, _, err = p.pullSchema1(ctx, manifestRef, v)
482
+		if err != nil {
483
+			return "", "", err
484
+		}
485
+	case *schema2.DeserializedManifest:
486
+		imageID, _, err = p.pullSchema2(ctx, manifestRef, v)
487
+		if err != nil {
488
+			return "", "", err
489
+		}
490
+	default:
491
+		return "", "", errors.New("unsupported manifest format")
492
+	}
493
+
494
+	return imageID, manifestListDigest, err
495
+}
496
+
497
+func (p *v2Puller) pullSchema2ImageConfig(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) {
498
+	blobs := p.repo.Blobs(ctx)
499
+	configJSON, err = blobs.Get(ctx, dgst)
500
+	if err != nil {
501
+		return nil, err
502
+	}
503
+
504
+	// Verify image config digest
505
+	verifier, err := digest.NewDigestVerifier(dgst)
506
+	if err != nil {
507
+		return nil, err
508
+	}
509
+	if _, err := verifier.Write(configJSON); err != nil {
510
+		return nil, err
511
+	}
512
+	if !verifier.Verified() {
513
+		err := fmt.Errorf("image config verification failed for digest %s", dgst)
514
+		logrus.Error(err)
515
+		return nil, err
516
+	}
517
+
518
+	return configJSON, nil
519
+}
520
+
521
+// schema2ManifestDigest computes the manifest digest, and, if pulling by
522
+// digest, ensures that it matches the requested digest.
523
+func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (digest.Digest, error) {
524
+	_, canonical, err := mfst.Payload()
525
+	if err != nil {
526
+		return "", err
527
+	}
528
+
529
+	// If pull by digest, then verify the manifest digest.
530
+	if digested, isDigested := ref.(reference.Canonical); isDigested {
531
+		verifier, err := digest.NewDigestVerifier(digested.Digest())
532
+		if err != nil {
533
+			return "", err
534
+		}
535
+		if _, err := verifier.Write(canonical); err != nil {
536
+			return "", err
537
+		}
538
+		if !verifier.Verified() {
539
+			err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
540
+			logrus.Error(err)
541
+			return "", err
542
+		}
543
+		return digested.Digest(), nil
544
+	}
545
+
546
+	return digest.FromBytes(canonical), nil
333 547
 }
334 548
 
335 549
 // allowV1Fallback checks if the error is a possible reason to fallback to v1
... ...
@@ -353,7 +617,7 @@ func allowV1Fallback(err error) error {
353 353
 	return err
354 354
 }
355 355
 
356
-func verifyManifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) {
356
+func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) {
357 357
 	// If pull by digest, then verify the manifest digest. NOTE: It is
358 358
 	// important to do this first, before any other content validation. If the
359 359
 	// digest cannot be verified, don't even bother with those other things.
... ...
@@ -362,13 +626,7 @@ func verifyManifest(signedManifest *schema1.SignedManifest, ref reference.Named)
362 362
 		if err != nil {
363 363
 			return nil, err
364 364
 		}
365
-		payload, err := signedManifest.Payload()
366
-		if err != nil {
367
-			// If this failed, the signatures section was corrupted
368
-			// or missing. Treat the entire manifest as the payload.
369
-			payload = signedManifest.Raw
370
-		}
371
-		if _, err := verifier.Write(payload); err != nil {
365
+		if _, err := verifier.Write(signedManifest.Canonical); err != nil {
372 366
 			return nil, err
373 367
 		}
374 368
 		if !verifier.Verified() {
... ...
@@ -376,15 +634,8 @@ func verifyManifest(signedManifest *schema1.SignedManifest, ref reference.Named)
376 376
 			logrus.Error(err)
377 377
 			return nil, err
378 378
 		}
379
-
380
-		var verifiedManifest schema1.Manifest
381
-		if err = json.Unmarshal(payload, &verifiedManifest); err != nil {
382
-			return nil, err
383
-		}
384
-		m = &verifiedManifest
385
-	} else {
386
-		m = &signedManifest.Manifest
387 379
 	}
380
+	m = &signedManifest.Manifest
388 381
 
389 382
 	if m.SchemaVersion != 1 {
390 383
 		return nil, fmt.Errorf("unsupported schema version %d for %q", m.SchemaVersion, ref.String())
... ...
@@ -123,7 +123,7 @@ func TestValidateManifest(t *testing.T) {
123 123
 		t.Fatal("error unmarshaling manifest:", err)
124 124
 	}
125 125
 
126
-	verifiedManifest, err := verifyManifest(&goodSignedManifest, expectedDigest)
126
+	verifiedManifest, err := verifySchema1Manifest(&goodSignedManifest, expectedDigest)
127 127
 	if err != nil {
128 128
 		t.Fatal("validateManifest failed:", err)
129 129
 	}
... ...
@@ -145,7 +145,7 @@ func TestValidateManifest(t *testing.T) {
145 145
 		t.Fatal("error unmarshaling manifest:", err)
146 146
 	}
147 147
 
148
-	verifiedManifest, err = verifyManifest(&extraDataSignedManifest, expectedDigest)
148
+	verifiedManifest, err = verifySchema1Manifest(&extraDataSignedManifest, expectedDigest)
149 149
 	if err != nil {
150 150
 		t.Fatal("validateManifest failed:", err)
151 151
 	}
... ...
@@ -167,7 +167,7 @@ func TestValidateManifest(t *testing.T) {
167 167
 		t.Fatal("error unmarshaling manifest:", err)
168 168
 	}
169 169
 
170
-	verifiedManifest, err = verifyManifest(&badSignedManifest, expectedDigest)
170
+	verifiedManifest, err = verifySchema1Manifest(&badSignedManifest, expectedDigest)
171 171
 	if err == nil || !strings.HasPrefix(err.Error(), "image verification failed for digest") {
172 172
 		t.Fatal("expected validateManifest to fail with digest error")
173 173
 	}
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"io"
8 8
 
9 9
 	"github.com/Sirupsen/logrus"
10
-	"github.com/docker/distribution/digest"
11 10
 	"github.com/docker/docker/distribution/metadata"
12 11
 	"github.com/docker/docker/distribution/xfer"
13 12
 	"github.com/docker/docker/image"
... ...
@@ -77,7 +76,6 @@ func NewPusher(ref reference.Named, endpoint registry.APIEndpoint, repoInfo *reg
77 77
 			endpoint:       endpoint,
78 78
 			repoInfo:       repoInfo,
79 79
 			config:         imagePushConfig,
80
-			layersPushed:   pushMap{layersPushed: make(map[digest.Digest]bool)},
81 80
 		}, nil
82 81
 	case registry.APIVersion1:
83 82
 		return &v1Pusher{
... ...
@@ -1,22 +1,20 @@
1 1
 package distribution
2 2
 
3 3
 import (
4
-	"encoding/json"
5 4
 	"errors"
6 5
 	"fmt"
7 6
 	"io"
8 7
 	"sync"
9
-	"time"
10 8
 
11 9
 	"github.com/Sirupsen/logrus"
12 10
 	"github.com/docker/distribution"
13 11
 	"github.com/docker/distribution/digest"
14
-	"github.com/docker/distribution/manifest"
15 12
 	"github.com/docker/distribution/manifest/schema1"
13
+	"github.com/docker/distribution/manifest/schema2"
14
+	"github.com/docker/distribution/registry/client"
16 15
 	"github.com/docker/docker/distribution/metadata"
17 16
 	"github.com/docker/docker/distribution/xfer"
18 17
 	"github.com/docker/docker/image"
19
-	"github.com/docker/docker/image/v1"
20 18
 	"github.com/docker/docker/layer"
21 19
 	"github.com/docker/docker/pkg/ioutils"
22 20
 	"github.com/docker/docker/pkg/progress"
... ...
@@ -43,75 +41,75 @@ type v2Pusher struct {
43 43
 	config         *ImagePushConfig
44 44
 	repo           distribution.Repository
45 45
 
46
-	// confirmedV2 is set to true if we confirm we're talking to a v2
47
-	// registry. This is used to limit fallbacks to the v1 protocol.
48
-	confirmedV2 bool
49
-
50
-	// layersPushed is the set of layers known to exist on the remote side.
51
-	// This avoids redundant queries when pushing multiple tags that
52
-	// involve the same layers.
53
-	layersPushed pushMap
46
+	// pushState is state built by the Download functions.
47
+	pushState pushState
54 48
 }
55 49
 
56
-type pushMap struct {
50
+type pushState struct {
57 51
 	sync.Mutex
58
-	layersPushed map[digest.Digest]bool
52
+	// remoteLayers is the set of layers known to exist on the remote side.
53
+	// This avoids redundant queries when pushing multiple tags that
54
+	// involve the same layers. It is also used to fill in digest and size
55
+	// information when building the manifest.
56
+	remoteLayers map[layer.DiffID]distribution.Descriptor
57
+	// confirmedV2 is set to true if we confirm we're talking to a v2
58
+	// registry. This is used to limit fallbacks to the v1 protocol.
59
+	confirmedV2 bool
59 60
 }
60 61
 
61 62
 func (p *v2Pusher) Push(ctx context.Context) (err error) {
62
-	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
63
+	p.pushState.remoteLayers = make(map[layer.DiffID]distribution.Descriptor)
64
+
65
+	p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
63 66
 	if err != nil {
64 67
 		logrus.Debugf("Error getting v2 registry: %v", err)
65
-		return fallbackError{err: err, confirmedV2: p.confirmedV2}
68
+		return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
66 69
 	}
67 70
 
68 71
 	if err = p.pushV2Repository(ctx); err != nil {
69 72
 		if registry.ContinueOnError(err) {
70
-			return fallbackError{err: err, confirmedV2: p.confirmedV2}
73
+			return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
71 74
 		}
72 75
 	}
73 76
 	return err
74 77
 }
75 78
 
76 79
 func (p *v2Pusher) pushV2Repository(ctx context.Context) (err error) {
77
-	var associations []reference.Association
78
-	if _, isTagged := p.ref.(reference.NamedTagged); isTagged {
80
+	if namedTagged, isNamedTagged := p.ref.(reference.NamedTagged); isNamedTagged {
79 81
 		imageID, err := p.config.ReferenceStore.Get(p.ref)
80 82
 		if err != nil {
81 83
 			return fmt.Errorf("tag does not exist: %s", p.ref.String())
82 84
 		}
83 85
 
84
-		associations = []reference.Association{
85
-			{
86
-				Ref:     p.ref,
87
-				ImageID: imageID,
88
-			},
89
-		}
90
-	} else {
91
-		// Pull all tags
92
-		associations = p.config.ReferenceStore.ReferencesByName(p.ref)
93
-	}
94
-	if err != nil {
95
-		return fmt.Errorf("error getting tags for %s: %s", p.repoInfo.Name(), err)
86
+		return p.pushV2Tag(ctx, namedTagged, imageID)
96 87
 	}
97
-	if len(associations) == 0 {
98
-		return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
88
+
89
+	if !reference.IsNameOnly(p.ref) {
90
+		return errors.New("cannot push a digest reference")
99 91
 	}
100 92
 
101
-	for _, association := range associations {
102
-		if err := p.pushV2Tag(ctx, association); err != nil {
103
-			return err
93
+	// Pull all tags
94
+	pushed := 0
95
+	for _, association := range p.config.ReferenceStore.ReferencesByName(p.ref) {
96
+		if namedTagged, isNamedTagged := association.Ref.(reference.NamedTagged); isNamedTagged {
97
+			pushed++
98
+			if err := p.pushV2Tag(ctx, namedTagged, association.ImageID); err != nil {
99
+				return err
100
+			}
104 101
 		}
105 102
 	}
106 103
 
104
+	if pushed == 0 {
105
+		return fmt.Errorf("no tags to push for %s", p.repoInfo.Name())
106
+	}
107
+
107 108
 	return nil
108 109
 }
109 110
 
110
-func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Association) error {
111
-	ref := association.Ref
111
+func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, imageID image.ID) error {
112 112
 	logrus.Debugf("Pushing repository: %s", ref.String())
113 113
 
114
-	img, err := p.config.ImageStore.Get(association.ImageID)
114
+	img, err := p.config.ImageStore.Get(imageID)
115 115
 	if err != nil {
116 116
 		return fmt.Errorf("could not find image from tag %s: %v", ref.String(), err)
117 117
 	}
... ...
@@ -134,18 +132,7 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Associat
134 134
 	descriptorTemplate := v2PushDescriptor{
135 135
 		blobSumService: p.blobSumService,
136 136
 		repo:           p.repo,
137
-		layersPushed:   &p.layersPushed,
138
-		confirmedV2:    &p.confirmedV2,
139
-	}
140
-
141
-	// Push empty layer if necessary
142
-	for _, h := range img.History {
143
-		if h.EmptyLayer {
144
-			descriptor := descriptorTemplate
145
-			descriptor.layer = layer.EmptyLayer
146
-			descriptors = []xfer.UploadDescriptor{&descriptor}
147
-			break
148
-		}
137
+		pushState:      &p.pushState,
149 138
 	}
150 139
 
151 140
 	// Loop bounds condition is to avoid pushing the base layer on Windows.
... ...
@@ -157,52 +144,75 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, association reference.Associat
157 157
 		l = l.Parent()
158 158
 	}
159 159
 
160
-	fsLayers, err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput)
161
-	if err != nil {
160
+	if err := p.config.UploadManager.Upload(ctx, descriptors, p.config.ProgressOutput); err != nil {
162 161
 		return err
163 162
 	}
164 163
 
165
-	var tag string
166
-	if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
167
-		tag = tagged.Tag()
168
-	}
169
-	m, err := CreateV2Manifest(p.repo.Name(), tag, img, fsLayers)
164
+	// Try schema2 first
165
+	builder := schema2.NewManifestBuilder(p.repo.Blobs(ctx), img.RawJSON())
166
+	manifest, err := manifestFromBuilder(ctx, builder, descriptors)
170 167
 	if err != nil {
171 168
 		return err
172 169
 	}
173 170
 
174
-	logrus.Infof("Signed manifest for %s using daemon's key: %s", ref.String(), p.config.TrustKey.KeyID())
175
-	signed, err := schema1.Sign(m, p.config.TrustKey)
171
+	manSvc, err := p.repo.Manifests(ctx)
176 172
 	if err != nil {
177 173
 		return err
178 174
 	}
179 175
 
180
-	manifestDigest, manifestSize, err := digestFromManifest(signed, ref)
181
-	if err != nil {
182
-		return err
176
+	putOptions := []distribution.ManifestServiceOption{client.WithTag(ref.Tag())}
177
+	if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
178
+		logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
179
+
180
+		builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, p.repo.Name(), ref.Tag(), img.RawJSON())
181
+		manifest, err = manifestFromBuilder(ctx, builder, descriptors)
182
+		if err != nil {
183
+			return err
184
+		}
185
+
186
+		if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
187
+			return err
188
+		}
183 189
 	}
184
-	if manifestDigest != "" {
185
-		if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
186
-			progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", tagged.Tag(), manifestDigest, manifestSize)
187
-			// Signal digest to the trust client so it can sign the
188
-			// push, if appropriate.
189
-			progress.Aux(p.config.ProgressOutput, PushResult{Tag: tagged.Tag(), Digest: manifestDigest, Size: manifestSize})
190
+
191
+	var canonicalManifest []byte
192
+
193
+	switch v := manifest.(type) {
194
+	case *schema1.SignedManifest:
195
+		canonicalManifest = v.Canonical
196
+	case *schema2.DeserializedManifest:
197
+		_, canonicalManifest, err = v.Payload()
198
+		if err != nil {
199
+			return err
190 200
 		}
191 201
 	}
192 202
 
193
-	manSvc, err := p.repo.Manifests(ctx)
194
-	if err != nil {
195
-		return err
203
+	manifestDigest := digest.FromBytes(canonicalManifest)
204
+	progress.Messagef(p.config.ProgressOutput, "", "%s: digest: %s size: %d", ref.Tag(), manifestDigest, len(canonicalManifest))
205
+	// Signal digest to the trust client so it can sign the
206
+	// push, if appropriate.
207
+	progress.Aux(p.config.ProgressOutput, PushResult{Tag: ref.Tag(), Digest: manifestDigest, Size: len(canonicalManifest)})
208
+
209
+	return nil
210
+}
211
+
212
+func manifestFromBuilder(ctx context.Context, builder distribution.ManifestBuilder, descriptors []xfer.UploadDescriptor) (distribution.Manifest, error) {
213
+	// descriptors is in reverse order; iterate backwards to get references
214
+	// appended in the right order.
215
+	for i := len(descriptors) - 1; i >= 0; i-- {
216
+		if err := builder.AppendReference(descriptors[i].(*v2PushDescriptor)); err != nil {
217
+			return nil, err
218
+		}
196 219
 	}
197
-	return manSvc.Put(signed)
220
+
221
+	return builder.Build(ctx)
198 222
 }
199 223
 
200 224
 type v2PushDescriptor struct {
201 225
 	layer          layer.Layer
202 226
 	blobSumService *metadata.BlobSumService
203 227
 	repo           distribution.Repository
204
-	layersPushed   *pushMap
205
-	confirmedV2    *bool
228
+	pushState      *pushState
206 229
 }
207 230
 
208 231
 func (pd *v2PushDescriptor) Key() string {
... ...
@@ -217,25 +227,38 @@ func (pd *v2PushDescriptor) DiffID() layer.DiffID {
217 217
 	return pd.layer.DiffID()
218 218
 }
219 219
 
220
-func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (digest.Digest, error) {
220
+func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.Output) error {
221 221
 	diffID := pd.DiffID()
222 222
 
223
-	logrus.Debugf("Pushing layer: %s", diffID)
223
+	pd.pushState.Lock()
224
+	if _, ok := pd.pushState.remoteLayers[diffID]; ok {
225
+		// it is already known that the push is not needed and
226
+		// therefore doing a stat is unnecessary
227
+		pd.pushState.Unlock()
228
+		progress.Update(progressOutput, pd.ID(), "Layer already exists")
229
+		return nil
230
+	}
231
+	pd.pushState.Unlock()
224 232
 
225 233
 	// Do we have any blobsums associated with this layer's DiffID?
226 234
 	possibleBlobsums, err := pd.blobSumService.GetBlobSums(diffID)
227 235
 	if err == nil {
228
-		dgst, exists, err := blobSumAlreadyExists(ctx, possibleBlobsums, pd.repo, pd.layersPushed)
236
+		descriptor, exists, err := blobSumAlreadyExists(ctx, possibleBlobsums, pd.repo, pd.pushState)
229 237
 		if err != nil {
230 238
 			progress.Update(progressOutput, pd.ID(), "Image push failed")
231
-			return "", retryOnError(err)
239
+			return retryOnError(err)
232 240
 		}
233 241
 		if exists {
234 242
 			progress.Update(progressOutput, pd.ID(), "Layer already exists")
235
-			return dgst, nil
243
+			pd.pushState.Lock()
244
+			pd.pushState.remoteLayers[diffID] = descriptor
245
+			pd.pushState.Unlock()
246
+			return nil
236 247
 		}
237 248
 	}
238 249
 
250
+	logrus.Debugf("Pushing layer: %s", diffID)
251
+
239 252
 	// if digest was empty or not saved, or if blob does not exist on the remote repository,
240 253
 	// then push the blob.
241 254
 	bs := pd.repo.Blobs(ctx)
... ...
@@ -243,13 +266,13 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
243 243
 	// Send the layer
244 244
 	layerUpload, err := bs.Create(ctx)
245 245
 	if err != nil {
246
-		return "", retryOnError(err)
246
+		return retryOnError(err)
247 247
 	}
248 248
 	defer layerUpload.Close()
249 249
 
250 250
 	arch, err := pd.layer.TarStream()
251 251
 	if err != nil {
252
-		return "", xfer.DoNotRetry{Err: err}
252
+		return xfer.DoNotRetry{Err: err}
253 253
 	}
254 254
 
255 255
 	// don't care if this fails; best effort
... ...
@@ -265,183 +288,62 @@ func (pd *v2PushDescriptor) Upload(ctx context.Context, progressOutput progress.
265 265
 	nn, err := layerUpload.ReadFrom(tee)
266 266
 	compressedReader.Close()
267 267
 	if err != nil {
268
-		return "", retryOnError(err)
268
+		return retryOnError(err)
269 269
 	}
270 270
 
271 271
 	pushDigest := digester.Digest()
272 272
 	if _, err := layerUpload.Commit(ctx, distribution.Descriptor{Digest: pushDigest}); err != nil {
273
-		return "", retryOnError(err)
273
+		return retryOnError(err)
274 274
 	}
275 275
 
276
-	// If Commit succeded, that's an indication that the remote registry
277
-	// speaks the v2 protocol.
278
-	*pd.confirmedV2 = true
279
-
280 276
 	logrus.Debugf("uploaded layer %s (%s), %d bytes", diffID, pushDigest, nn)
281 277
 	progress.Update(progressOutput, pd.ID(), "Pushed")
282 278
 
283 279
 	// Cache mapping from this layer's DiffID to the blobsum
284 280
 	if err := pd.blobSumService.Add(diffID, pushDigest); err != nil {
285
-		return "", xfer.DoNotRetry{Err: err}
281
+		return xfer.DoNotRetry{Err: err}
282
+	}
283
+
284
+	pd.pushState.Lock()
285
+
286
+	// If Commit succeded, that's an indication that the remote registry
287
+	// speaks the v2 protocol.
288
+	pd.pushState.confirmedV2 = true
289
+
290
+	pd.pushState.remoteLayers[diffID] = distribution.Descriptor{
291
+		Digest:    pushDigest,
292
+		MediaType: schema2.MediaTypeLayer,
293
+		Size:      nn,
286 294
 	}
287 295
 
288
-	pd.layersPushed.Lock()
289
-	pd.layersPushed.layersPushed[pushDigest] = true
290
-	pd.layersPushed.Unlock()
296
+	pd.pushState.Unlock()
291 297
 
292
-	return pushDigest, nil
298
+	return nil
299
+}
300
+
301
+func (pd *v2PushDescriptor) Descriptor() distribution.Descriptor {
302
+	// Not necessary to lock pushStatus because this is always
303
+	// called after all the mutation in pushStatus.
304
+	// By the time this function is called, every layer will have
305
+	// an entry in remoteLayers.
306
+	return pd.pushState.remoteLayers[pd.DiffID()]
293 307
 }
294 308
 
295 309
 // blobSumAlreadyExists checks if the registry already know about any of the
296 310
 // blobsums passed in the "blobsums" slice. If it finds one that the registry
297 311
 // knows about, it returns the known digest and "true".
298
-func blobSumAlreadyExists(ctx context.Context, blobsums []digest.Digest, repo distribution.Repository, layersPushed *pushMap) (digest.Digest, bool, error) {
299
-	layersPushed.Lock()
300
-	for _, dgst := range blobsums {
301
-		if layersPushed.layersPushed[dgst] {
302
-			// it is already known that the push is not needed and
303
-			// therefore doing a stat is unnecessary
304
-			layersPushed.Unlock()
305
-			return dgst, true, nil
306
-		}
307
-	}
308
-	layersPushed.Unlock()
309
-
312
+func blobSumAlreadyExists(ctx context.Context, blobsums []digest.Digest, repo distribution.Repository, pushState *pushState) (distribution.Descriptor, bool, error) {
310 313
 	for _, dgst := range blobsums {
311
-		_, err := repo.Blobs(ctx).Stat(ctx, dgst)
314
+		descriptor, err := repo.Blobs(ctx).Stat(ctx, dgst)
312 315
 		switch err {
313 316
 		case nil:
314
-			return dgst, true, nil
317
+			descriptor.MediaType = schema2.MediaTypeLayer
318
+			return descriptor, true, nil
315 319
 		case distribution.ErrBlobUnknown:
316 320
 			// nop
317 321
 		default:
318
-			return "", false, err
322
+			return distribution.Descriptor{}, false, err
319 323
 		}
320 324
 	}
321
-	return "", false, nil
322
-}
323
-
324
-// CreateV2Manifest creates a V2 manifest from an image config and set of
325
-// FSLayer digests.
326
-// FIXME: This should be moved to the distribution repo, since it will also
327
-// be useful for converting new manifests to the old format.
328
-func CreateV2Manifest(name, tag string, img *image.Image, fsLayers map[layer.DiffID]digest.Digest) (*schema1.Manifest, error) {
329
-	if len(img.History) == 0 {
330
-		return nil, errors.New("empty history when trying to create V2 manifest")
331
-	}
332
-
333
-	// Generate IDs for each layer
334
-	// For non-top-level layers, create fake V1Compatibility strings that
335
-	// fit the format and don't collide with anything else, but don't
336
-	// result in runnable images on their own.
337
-	type v1Compatibility struct {
338
-		ID              string    `json:"id"`
339
-		Parent          string    `json:"parent,omitempty"`
340
-		Comment         string    `json:"comment,omitempty"`
341
-		Created         time.Time `json:"created"`
342
-		ContainerConfig struct {
343
-			Cmd []string
344
-		} `json:"container_config,omitempty"`
345
-		ThrowAway bool `json:"throwaway,omitempty"`
346
-	}
347
-
348
-	fsLayerList := make([]schema1.FSLayer, len(img.History))
349
-	history := make([]schema1.History, len(img.History))
350
-
351
-	parent := ""
352
-	layerCounter := 0
353
-	for i, h := range img.History {
354
-		if i == len(img.History)-1 {
355
-			break
356
-		}
357
-
358
-		var diffID layer.DiffID
359
-		if h.EmptyLayer {
360
-			diffID = layer.EmptyLayer.DiffID()
361
-		} else {
362
-			if len(img.RootFS.DiffIDs) <= layerCounter {
363
-				return nil, errors.New("too many non-empty layers in History section")
364
-			}
365
-			diffID = img.RootFS.DiffIDs[layerCounter]
366
-			layerCounter++
367
-		}
368
-
369
-		fsLayer, present := fsLayers[diffID]
370
-		if !present {
371
-			return nil, fmt.Errorf("missing layer in CreateV2Manifest: %s", diffID.String())
372
-		}
373
-		dgst, err := digest.FromBytes([]byte(fsLayer.Hex() + " " + parent))
374
-		if err != nil {
375
-			return nil, err
376
-		}
377
-		v1ID := dgst.Hex()
378
-
379
-		v1Compatibility := v1Compatibility{
380
-			ID:      v1ID,
381
-			Parent:  parent,
382
-			Comment: h.Comment,
383
-			Created: h.Created,
384
-		}
385
-		v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
386
-		if h.EmptyLayer {
387
-			v1Compatibility.ThrowAway = true
388
-		}
389
-		jsonBytes, err := json.Marshal(&v1Compatibility)
390
-		if err != nil {
391
-			return nil, err
392
-		}
393
-
394
-		reversedIndex := len(img.History) - i - 1
395
-		history[reversedIndex].V1Compatibility = string(jsonBytes)
396
-		fsLayerList[reversedIndex] = schema1.FSLayer{BlobSum: fsLayer}
397
-
398
-		parent = v1ID
399
-	}
400
-
401
-	latestHistory := img.History[len(img.History)-1]
402
-
403
-	var diffID layer.DiffID
404
-	if latestHistory.EmptyLayer {
405
-		diffID = layer.EmptyLayer.DiffID()
406
-	} else {
407
-		if len(img.RootFS.DiffIDs) <= layerCounter {
408
-			return nil, errors.New("too many non-empty layers in History section")
409
-		}
410
-		diffID = img.RootFS.DiffIDs[layerCounter]
411
-	}
412
-	fsLayer, present := fsLayers[diffID]
413
-	if !present {
414
-		return nil, fmt.Errorf("missing layer in CreateV2Manifest: %s", diffID.String())
415
-	}
416
-
417
-	dgst, err := digest.FromBytes([]byte(fsLayer.Hex() + " " + parent + " " + string(img.RawJSON())))
418
-	if err != nil {
419
-		return nil, err
420
-	}
421
-	fsLayerList[0] = schema1.FSLayer{BlobSum: fsLayer}
422
-
423
-	// Top-level v1compatibility string should be a modified version of the
424
-	// image config.
425
-	transformedConfig, err := v1.MakeV1ConfigFromConfig(img, dgst.Hex(), parent, latestHistory.EmptyLayer)
426
-	if err != nil {
427
-		return nil, err
428
-	}
429
-
430
-	history[0].V1Compatibility = string(transformedConfig)
431
-
432
-	// windows-only baselayer setup
433
-	if err := setupBaseLayer(history, *img.RootFS); err != nil {
434
-		return nil, err
435
-	}
436
-
437
-	return &schema1.Manifest{
438
-		Versioned: manifest.Versioned{
439
-			SchemaVersion: 1,
440
-		},
441
-		Name:         name,
442
-		Tag:          tag,
443
-		Architecture: img.Architecture,
444
-		FSLayers:     fsLayerList,
445
-		History:      history,
446
-	}, nil
325
+	return distribution.Descriptor{}, false, nil
447 326
 }
448 327
deleted file mode 100644
... ...
@@ -1,176 +0,0 @@
1
-package distribution
2
-
3
-import (
4
-	"reflect"
5
-	"testing"
6
-
7
-	"github.com/docker/distribution/digest"
8
-	"github.com/docker/distribution/manifest/schema1"
9
-	"github.com/docker/docker/image"
10
-	"github.com/docker/docker/layer"
11
-)
12
-
13
-func TestCreateV2Manifest(t *testing.T) {
14
-	imgJSON := `{
15
-    "architecture": "amd64",
16
-    "config": {
17
-        "AttachStderr": false,
18
-        "AttachStdin": false,
19
-        "AttachStdout": false,
20
-        "Cmd": [
21
-            "/bin/sh",
22
-            "-c",
23
-            "echo hi"
24
-        ],
25
-        "Domainname": "",
26
-        "Entrypoint": null,
27
-        "Env": [
28
-            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
29
-            "derived=true",
30
-            "asdf=true"
31
-        ],
32
-        "Hostname": "23304fc829f9",
33
-        "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
34
-        "Labels": {},
35
-        "OnBuild": [],
36
-        "OpenStdin": false,
37
-        "StdinOnce": false,
38
-        "Tty": false,
39
-        "User": "",
40
-        "Volumes": null,
41
-        "WorkingDir": ""
42
-    },
43
-    "container": "e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001",
44
-    "container_config": {
45
-        "AttachStderr": false,
46
-        "AttachStdin": false,
47
-        "AttachStdout": false,
48
-        "Cmd": [
49
-            "/bin/sh",
50
-            "-c",
51
-            "#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"
52
-        ],
53
-        "Domainname": "",
54
-        "Entrypoint": null,
55
-        "Env": [
56
-            "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
57
-            "derived=true",
58
-            "asdf=true"
59
-        ],
60
-        "Hostname": "23304fc829f9",
61
-        "Image": "sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246",
62
-        "Labels": {},
63
-        "OnBuild": [],
64
-        "OpenStdin": false,
65
-        "StdinOnce": false,
66
-        "Tty": false,
67
-        "User": "",
68
-        "Volumes": null,
69
-        "WorkingDir": ""
70
-    },
71
-    "created": "2015-11-04T23:06:32.365666163Z",
72
-    "docker_version": "1.9.0-dev",
73
-    "history": [
74
-        {
75
-            "created": "2015-10-31T22:22:54.690851953Z",
76
-            "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
77
-        },
78
-        {
79
-            "created": "2015-10-31T22:22:55.613815829Z",
80
-            "created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
81
-        },
82
-        {
83
-            "created": "2015-11-04T23:06:30.934316144Z",
84
-            "created_by": "/bin/sh -c #(nop) ENV derived=true",
85
-            "empty_layer": true
86
-        },
87
-        {
88
-            "created": "2015-11-04T23:06:31.192097572Z",
89
-            "created_by": "/bin/sh -c #(nop) ENV asdf=true",
90
-            "empty_layer": true
91
-        },
92
-        {
93
-            "created": "2015-11-04T23:06:32.083868454Z",
94
-            "created_by": "/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"
95
-        },
96
-        {
97
-            "created": "2015-11-04T23:06:32.365666163Z",
98
-            "created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]",
99
-            "empty_layer": true
100
-        }
101
-    ],
102
-    "os": "linux",
103
-    "rootfs": {
104
-        "diff_ids": [
105
-            "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
106
-            "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
107
-            "sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49"
108
-        ],
109
-        "type": "layers"
110
-    }
111
-}`
112
-
113
-	// To fill in rawJSON
114
-	img, err := image.NewFromJSON([]byte(imgJSON))
115
-	if err != nil {
116
-		t.Fatalf("json decoding failed: %v", err)
117
-	}
118
-
119
-	fsLayers := map[layer.DiffID]digest.Digest{
120
-		layer.DiffID("sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1"): digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
121
-		layer.DiffID("sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"): digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa"),
122
-		layer.DiffID("sha256:13f53e08df5a220ab6d13c58b2bf83a59cbdc2e04d0a3f041ddf4b0ba4112d49"): digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"),
123
-	}
124
-
125
-	manifest, err := CreateV2Manifest("testrepo", "testtag", img, fsLayers)
126
-	if err != nil {
127
-		t.Fatalf("CreateV2Manifest returned error: %v", err)
128
-	}
129
-
130
-	if manifest.Versioned.SchemaVersion != 1 {
131
-		t.Fatal("SchemaVersion != 1")
132
-	}
133
-	if manifest.Name != "testrepo" {
134
-		t.Fatal("incorrect name in manifest")
135
-	}
136
-	if manifest.Tag != "testtag" {
137
-		t.Fatal("incorrect tag in manifest")
138
-	}
139
-	if manifest.Architecture != "amd64" {
140
-		t.Fatal("incorrect arch in manifest")
141
-	}
142
-
143
-	expectedFSLayers := []schema1.FSLayer{
144
-		{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
145
-		{BlobSum: digest.Digest("sha256:b4ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
146
-		{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
147
-		{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
148
-		{BlobSum: digest.Digest("sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa")},
149
-		{BlobSum: digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")},
150
-	}
151
-
152
-	if len(manifest.FSLayers) != len(expectedFSLayers) {
153
-		t.Fatalf("wrong number of FSLayers: %d", len(manifest.FSLayers))
154
-	}
155
-	if !reflect.DeepEqual(manifest.FSLayers, expectedFSLayers) {
156
-		t.Fatal("wrong FSLayers list")
157
-	}
158
-
159
-	expectedV1Compatibility := []string{
160
-		`{"architecture":"amd64","config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","echo hi"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"container":"e91032eb0403a61bfe085ff5a5a48e3659e5a6deae9f4d678daa2ae399d5a001","container_config":{"AttachStderr":false,"AttachStdin":false,"AttachStdout":false,"Cmd":["/bin/sh","-c","#(nop) CMD [\"/bin/sh\" \"-c\" \"echo hi\"]"],"Domainname":"","Entrypoint":null,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","derived=true","asdf=true"],"Hostname":"23304fc829f9","Image":"sha256:4ab15c48b859c2920dd5224f92aabcd39a52794c5b3cf088fb3bbb438756c246","Labels":{},"OnBuild":[],"OpenStdin":false,"StdinOnce":false,"Tty":false,"User":"","Volumes":null,"WorkingDir":""},"created":"2015-11-04T23:06:32.365666163Z","docker_version":"1.9.0-dev","id":"d728140d3fd23dfcac505954af0b2224b3579b177029eded62916579eb19ac64","os":"linux","parent":"0594e66a9830fa5ba73b66349eb221ea4beb6bac8d2148b90a0f371f8d67bcd5","throwaway":true}`,
161
-		`{"id":"0594e66a9830fa5ba73b66349eb221ea4beb6bac8d2148b90a0f371f8d67bcd5","parent":"39bc0dbed47060dd8952b048e73744ae471fe50354d2c267d308292c53b83ce1","created":"2015-11-04T23:06:32.083868454Z","container_config":{"Cmd":["/bin/sh -c dd if=/dev/zero of=/file bs=1024 count=1024"]}}`,
162
-		`{"id":"39bc0dbed47060dd8952b048e73744ae471fe50354d2c267d308292c53b83ce1","parent":"875d7f206c023dc979e1677567a01364074f82b61e220c9b83a4610170490381","created":"2015-11-04T23:06:31.192097572Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV asdf=true"]},"throwaway":true}`,
163
-		`{"id":"875d7f206c023dc979e1677567a01364074f82b61e220c9b83a4610170490381","parent":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","created":"2015-11-04T23:06:30.934316144Z","container_config":{"Cmd":["/bin/sh -c #(nop) ENV derived=true"]},"throwaway":true}`,
164
-		`{"id":"9e3447ca24cb96d86ebd5960cb34d1299b07e0a0e03801d90b9969a2c187dd6e","parent":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:55.613815829Z","container_config":{"Cmd":["/bin/sh -c #(nop) CMD [\"sh\"]"]}}`,
165
-		`{"id":"3690474eb5b4b26fdfbd89c6e159e8cc376ca76ef48032a30fa6aafd56337880","created":"2015-10-31T22:22:54.690851953Z","container_config":{"Cmd":["/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"]}}`,
166
-	}
167
-
168
-	if len(manifest.History) != len(expectedV1Compatibility) {
169
-		t.Fatalf("wrong number of history entries: %d", len(manifest.History))
170
-	}
171
-	for i := range expectedV1Compatibility {
172
-		if manifest.History[i].V1Compatibility != expectedV1Compatibility[i] {
173
-			t.Fatalf("wrong V1Compatibility %d. expected:\n%s\ngot:\n%s", i, expectedV1Compatibility[i], manifest.History[i].V1Compatibility)
174
-		}
175
-	}
176
-}
177 1
deleted file mode 100644
... ...
@@ -1,12 +0,0 @@
1
-// +build !windows
2
-
3
-package distribution
4
-
5
-import (
6
-	"github.com/docker/distribution/manifest/schema1"
7
-	"github.com/docker/docker/image"
8
-)
9
-
10
-func setupBaseLayer(history []schema1.History, rootFS image.RootFS) error {
11
-	return nil
12
-}
13 1
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-// +build windows
2
-
3
-package distribution
4
-
5
-import (
6
-	"encoding/json"
7
-
8
-	"github.com/docker/distribution/manifest/schema1"
9
-	"github.com/docker/docker/image"
10
-)
11
-
12
-func setupBaseLayer(history []schema1.History, rootFS image.RootFS) error {
13
-	var v1Config map[string]*json.RawMessage
14
-	if err := json.Unmarshal([]byte(history[len(history)-1].V1Compatibility), &v1Config); err != nil {
15
-		return err
16
-	}
17
-	baseID, err := json.Marshal(rootFS.BaseLayerID())
18
-	if err != nil {
19
-		return err
20
-	}
21
-	v1Config["parent"] = (*json.RawMessage)(&baseID)
22
-	configJSON, err := json.Marshal(v1Config)
23
-	if err != nil {
24
-		return err
25
-	}
26
-	history[len(history)-1].V1Compatibility = string(configJSON)
27
-	return nil
28
-}
... ...
@@ -8,16 +8,12 @@ import (
8 8
 	"strings"
9 9
 	"time"
10 10
 
11
-	"github.com/Sirupsen/logrus"
12 11
 	"github.com/docker/distribution"
13
-	"github.com/docker/distribution/digest"
14
-	"github.com/docker/distribution/manifest/schema1"
15 12
 	"github.com/docker/distribution/registry/api/errcode"
16 13
 	"github.com/docker/distribution/registry/client"
17 14
 	"github.com/docker/distribution/registry/client/auth"
18 15
 	"github.com/docker/distribution/registry/client/transport"
19 16
 	"github.com/docker/docker/distribution/xfer"
20
-	"github.com/docker/docker/reference"
21 17
 	"github.com/docker/docker/registry"
22 18
 	"github.com/docker/engine-api/types"
23 19
 	"golang.org/x/net/context"
... ...
@@ -125,20 +121,6 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
125 125
 	return repo, foundVersion, err
126 126
 }
127 127
 
128
-func digestFromManifest(m *schema1.SignedManifest, name reference.Named) (digest.Digest, int, error) {
129
-	payload, err := m.Payload()
130
-	if err != nil {
131
-		// If this failed, the signatures section was corrupted
132
-		// or missing. Treat the entire manifest as the payload.
133
-		payload = m.Raw
134
-	}
135
-	manifestDigest, err := digest.FromBytes(payload)
136
-	if err != nil {
137
-		logrus.Infof("Could not compute manifest digest for %s:%s : %v", name.Name(), m.Tag, err)
138
-	}
139
-	return manifestDigest, len(payload), nil
140
-}
141
-
142 128
 type existingTokenHandler struct {
143 129
 	token string
144 130
 }
... ...
@@ -65,12 +65,7 @@ func createChainIDFromParent(parent layer.ChainID, dgsts ...layer.DiffID) layer.
65 65
 		return createChainIDFromParent(layer.ChainID(dgsts[0]), dgsts[1:]...)
66 66
 	}
67 67
 	// H = "H(n-1) SHA256(n)"
68
-	dgst, err := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
69
-	if err != nil {
70
-		// Digest calculation is not expected to throw an error,
71
-		// any error at this point is a program error
72
-		panic(err)
73
-	}
68
+	dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
74 69
 	return createChainIDFromParent(layer.ChainID(dgst), dgsts[1:]...)
75 70
 }
76 71
 
... ...
@@ -92,11 +87,7 @@ func (ls *mockLayerStore) Register(reader io.Reader, parentID layer.ChainID) (la
92 92
 	if err != nil {
93 93
 		return nil, err
94 94
 	}
95
-	diffID, err := digest.FromBytes(l.layerData.Bytes())
96
-	if err != nil {
97
-		return nil, err
98
-	}
99
-	l.diffID = layer.DiffID(diffID)
95
+	l.diffID = layer.DiffID(digest.FromBytes(l.layerData.Bytes()))
100 96
 	l.chainID = createChainIDFromParent(parentID, l.diffID)
101 97
 
102 98
 	ls.layers[l.chainID] = l
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"time"
6 6
 
7 7
 	"github.com/Sirupsen/logrus"
8
-	"github.com/docker/distribution/digest"
9 8
 	"github.com/docker/docker/layer"
10 9
 	"github.com/docker/docker/pkg/progress"
11 10
 	"golang.org/x/net/context"
... ...
@@ -30,7 +29,6 @@ type uploadTransfer struct {
30 30
 	Transfer
31 31
 
32 32
 	diffID layer.DiffID
33
-	digest digest.Digest
34 33
 	err    error
35 34
 }
36 35
 
... ...
@@ -43,16 +41,15 @@ type UploadDescriptor interface {
43 43
 	// DiffID should return the DiffID for this layer.
44 44
 	DiffID() layer.DiffID
45 45
 	// Upload is called to perform the Upload.
46
-	Upload(ctx context.Context, progressOutput progress.Output) (digest.Digest, error)
46
+	Upload(ctx context.Context, progressOutput progress.Output) error
47 47
 }
48 48
 
49 49
 // Upload is a blocking function which ensures the listed layers are present on
50 50
 // the remote registry. It uses the string returned by the Key method to
51 51
 // deduplicate uploads.
52
-func (lum *LayerUploadManager) Upload(ctx context.Context, layers []UploadDescriptor, progressOutput progress.Output) (map[layer.DiffID]digest.Digest, error) {
52
+func (lum *LayerUploadManager) Upload(ctx context.Context, layers []UploadDescriptor, progressOutput progress.Output) error {
53 53
 	var (
54 54
 		uploads          []*uploadTransfer
55
-		digests          = make(map[layer.DiffID]digest.Digest)
56 55
 		dedupDescriptors = make(map[string]struct{})
57 56
 	)
58 57
 
... ...
@@ -74,16 +71,15 @@ func (lum *LayerUploadManager) Upload(ctx context.Context, layers []UploadDescri
74 74
 	for _, upload := range uploads {
75 75
 		select {
76 76
 		case <-ctx.Done():
77
-			return nil, ctx.Err()
77
+			return ctx.Err()
78 78
 		case <-upload.Transfer.Done():
79 79
 			if upload.err != nil {
80
-				return nil, upload.err
80
+				return upload.err
81 81
 			}
82
-			digests[upload.diffID] = upload.digest
83 82
 		}
84 83
 	}
85 84
 
86
-	return digests, nil
85
+	return nil
87 86
 }
88 87
 
89 88
 func (lum *LayerUploadManager) makeUploadFunc(descriptor UploadDescriptor) DoFunc {
... ...
@@ -109,9 +105,8 @@ func (lum *LayerUploadManager) makeUploadFunc(descriptor UploadDescriptor) DoFun
109 109
 
110 110
 			retries := 0
111 111
 			for {
112
-				digest, err := descriptor.Upload(u.Transfer.Context(), progressOutput)
112
+				err := descriptor.Upload(u.Transfer.Context(), progressOutput)
113 113
 				if err == nil {
114
-					u.digest = digest
115 114
 					break
116 115
 				}
117 116
 
... ...
@@ -36,12 +36,12 @@ func (u *mockUploadDescriptor) DiffID() layer.DiffID {
36 36
 }
37 37
 
38 38
 // Upload is called to perform the upload.
39
-func (u *mockUploadDescriptor) Upload(ctx context.Context, progressOutput progress.Output) (digest.Digest, error) {
39
+func (u *mockUploadDescriptor) Upload(ctx context.Context, progressOutput progress.Output) error {
40 40
 	if u.currentUploads != nil {
41 41
 		defer atomic.AddInt32(u.currentUploads, -1)
42 42
 
43 43
 		if atomic.AddInt32(u.currentUploads, 1) > maxUploadConcurrency {
44
-			return "", errors.New("concurrency limit exceeded")
44
+			return errors.New("concurrency limit exceeded")
45 45
 		}
46 46
 	}
47 47
 
... ...
@@ -49,7 +49,7 @@ func (u *mockUploadDescriptor) Upload(ctx context.Context, progressOutput progre
49 49
 	for i := int64(0); i <= 10; i++ {
50 50
 		select {
51 51
 		case <-ctx.Done():
52
-			return "", ctx.Err()
52
+			return ctx.Err()
53 53
 		case <-time.After(10 * time.Millisecond):
54 54
 			progressOutput.WriteProgress(progress.Progress{ID: u.ID(), Current: i, Total: 10})
55 55
 		}
... ...
@@ -57,12 +57,10 @@ func (u *mockUploadDescriptor) Upload(ctx context.Context, progressOutput progre
57 57
 
58 58
 	if u.simulateRetries != 0 {
59 59
 		u.simulateRetries--
60
-		return "", errors.New("simulating retry")
60
+		return errors.New("simulating retry")
61 61
 	}
62 62
 
63
-	// For the mock implementation, use SHA256(DiffID) as the returned
64
-	// digest.
65
-	return digest.FromBytes([]byte(u.diffID.String()))
63
+	return nil
66 64
 }
67 65
 
68 66
 func uploadDescriptors(currentUploads *int32) []UploadDescriptor {
... ...
@@ -101,26 +99,13 @@ func TestSuccessfulUpload(t *testing.T) {
101 101
 	var currentUploads int32
102 102
 	descriptors := uploadDescriptors(&currentUploads)
103 103
 
104
-	digests, err := lum.Upload(context.Background(), descriptors, progress.ChanOutput(progressChan))
104
+	err := lum.Upload(context.Background(), descriptors, progress.ChanOutput(progressChan))
105 105
 	if err != nil {
106 106
 		t.Fatalf("upload error: %v", err)
107 107
 	}
108 108
 
109 109
 	close(progressChan)
110 110
 	<-progressDone
111
-
112
-	if len(digests) != len(expectedDigests) {
113
-		t.Fatal("wrong number of keys in digests map")
114
-	}
115
-
116
-	for key, val := range expectedDigests {
117
-		if digests[key] != val {
118
-			t.Fatalf("mismatch in digest array for key %v (expected %v, got %v)", key, val, digests[key])
119
-		}
120
-		if receivedProgress[key.String()] != 10 {
121
-			t.Fatalf("missing or wrong progress output for %v", key)
122
-		}
123
-	}
124 111
 }
125 112
 
126 113
 func TestCancelledUpload(t *testing.T) {
... ...
@@ -143,7 +128,7 @@ func TestCancelledUpload(t *testing.T) {
143 143
 	}()
144 144
 
145 145
 	descriptors := uploadDescriptors(nil)
146
-	_, err := lum.Upload(ctx, descriptors, progress.ChanOutput(progressChan))
146
+	err := lum.Upload(ctx, descriptors, progress.ChanOutput(progressChan))
147 147
 	if err != context.Canceled {
148 148
 		t.Fatal("expected upload to be cancelled")
149 149
 	}
... ...
@@ -44,7 +44,7 @@ clone git github.com/boltdb/bolt v1.1.0
44 44
 clone git github.com/miekg/dns d27455715200c7d3e321a1e5cadb27c9ee0b0f02
45 45
 
46 46
 # get graph and distribution packages
47
-clone git github.com/docker/distribution 568bf038af6d65b376165d02886b1c7fcaef1f61
47
+clone git github.com/docker/distribution a7ae88da459b98b481a245e5b1750134724ac67d
48 48
 clone git github.com/vbatts/tar-split v0.9.11
49 49
 
50 50
 # get desired notary commit, might also need to be updated in Dockerfile
... ...
@@ -101,11 +101,7 @@ func (s *fs) get(id ID) ([]byte, error) {
101 101
 	}
102 102
 
103 103
 	// todo: maybe optional
104
-	validated, err := digest.FromBytes(content)
105
-	if err != nil {
106
-		return nil, err
107
-	}
108
-	if ID(validated) != id {
104
+	if ID(digest.FromBytes(content)) != id {
109 105
 		return nil, fmt.Errorf("failed to verify image: %v", id)
110 106
 	}
111 107
 
... ...
@@ -121,11 +117,7 @@ func (s *fs) Set(data []byte) (ID, error) {
121 121
 		return "", fmt.Errorf("Invalid empty data")
122 122
 	}
123 123
 
124
-	dgst, err := digest.FromBytes(data)
125
-	if err != nil {
126
-		return "", err
127
-	}
128
-	id := ID(dgst)
124
+	id := ID(digest.FromBytes(data))
129 125
 	filePath := s.contentFile(id)
130 126
 	tempFilePath := s.contentFile(id) + ".tmp"
131 127
 	if err := ioutil.WriteFile(tempFilePath, data, 0600); err != nil {
... ...
@@ -67,10 +67,7 @@ func TestFSInvalidSet(t *testing.T) {
67 67
 		t.Fatal(err)
68 68
 	}
69 69
 
70
-	id, err := digest.FromBytes([]byte("foobar"))
71
-	if err != nil {
72
-		t.Fatal(err)
73
-	}
70
+	id := digest.FromBytes([]byte("foobar"))
74 71
 	err = os.Mkdir(filepath.Join(tmpdir, contentDirName, string(id.Algorithm()), id.Hex()), 0700)
75 72
 	if err != nil {
76 73
 		t.Fatal(err)
... ...
@@ -160,11 +157,7 @@ func testMetadataGetSet(t *testing.T, store StoreBackend) {
160 160
 		t.Fatal("Expected error for getting metadata for unknown key")
161 161
 	}
162 162
 
163
-	id3, err := digest.FromBytes([]byte("baz"))
164
-	if err != nil {
165
-		t.Fatal(err)
166
-	}
167
-
163
+	id3 := digest.FromBytes([]byte("baz"))
168 164
 	err = store.SetMetadata(ID(id3), "tkey", []byte("tval"))
169 165
 	if err == nil {
170 166
 		t.Fatal("Expected error for setting metadata for unknown ID.")
... ...
@@ -27,7 +27,7 @@ func (r *RootFS) BaseLayerID() string {
27 27
 
28 28
 // ChainID returns the ChainID for the top layer in RootFS.
29 29
 func (r *RootFS) ChainID() layer.ChainID {
30
-	baseDiffID, _ := digest.FromBytes([]byte(r.BaseLayerID())) // can never error
30
+	baseDiffID := digest.FromBytes([]byte(r.BaseLayerID()))
31 31
 	return layer.CreateChainID(append([]layer.DiffID{layer.DiffID(baseDiffID)}, r.DiffIDs...))
32 32
 }
33 33
 
... ...
@@ -63,7 +63,7 @@ func CreateID(v1Image image.V1Image, layerID layer.ChainID, parent digest.Digest
63 63
 	}
64 64
 	logrus.Debugf("CreateV1ID %s", configJSON)
65 65
 
66
-	return digest.FromBytes(configJSON)
66
+	return digest.FromBytes(configJSON), nil
67 67
 }
68 68
 
69 69
 // MakeConfigFromV1Config creates an image config from the legacy V1 config format.
... ...
@@ -48,7 +48,7 @@ type DockerRegistrySuite struct {
48 48
 
49 49
 func (s *DockerRegistrySuite) SetUpTest(c *check.C) {
50 50
 	testRequires(c, DaemonIsLinux)
51
-	s.reg = setupRegistry(c)
51
+	s.reg = setupRegistry(c, false)
52 52
 	s.d = NewDaemon(c)
53 53
 }
54 54
 
... ...
@@ -63,6 +63,34 @@ func (s *DockerRegistrySuite) TearDownTest(c *check.C) {
63 63
 }
64 64
 
65 65
 func init() {
66
+	check.Suite(&DockerSchema1RegistrySuite{
67
+		ds: &DockerSuite{},
68
+	})
69
+}
70
+
71
+type DockerSchema1RegistrySuite struct {
72
+	ds  *DockerSuite
73
+	reg *testRegistryV2
74
+	d   *Daemon
75
+}
76
+
77
+func (s *DockerSchema1RegistrySuite) SetUpTest(c *check.C) {
78
+	testRequires(c, DaemonIsLinux)
79
+	s.reg = setupRegistry(c, true)
80
+	s.d = NewDaemon(c)
81
+}
82
+
83
+func (s *DockerSchema1RegistrySuite) TearDownTest(c *check.C) {
84
+	if s.reg != nil {
85
+		s.reg.Close()
86
+	}
87
+	if s.ds != nil {
88
+		s.ds.TearDownTest(c)
89
+	}
90
+	s.d.Stop()
91
+}
92
+
93
+func init() {
66 94
 	check.Suite(&DockerDaemonSuite{
67 95
 		ds: &DockerSuite{},
68 96
 	})
... ...
@@ -97,7 +125,7 @@ type DockerTrustSuite struct {
97 97
 }
98 98
 
99 99
 func (s *DockerTrustSuite) SetUpTest(c *check.C) {
100
-	s.reg = setupRegistry(c)
100
+	s.reg = setupRegistry(c, false)
101 101
 	s.not = setupNotary(c)
102 102
 }
103 103
 
... ...
@@ -10,6 +10,7 @@ import (
10 10
 
11 11
 	"github.com/docker/distribution/digest"
12 12
 	"github.com/docker/distribution/manifest/schema1"
13
+	"github.com/docker/distribution/manifest/schema2"
13 14
 	"github.com/docker/docker/pkg/integration/checker"
14 15
 	"github.com/docker/docker/pkg/stringutils"
15 16
 	"github.com/docker/engine-api/types"
... ...
@@ -56,7 +57,7 @@ func setupImageWithTag(c *check.C, tag string) (digest.Digest, error) {
56 56
 	return digest.Digest(pushDigest), nil
57 57
 }
58 58
 
59
-func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
59
+func testPullByTagDisplaysDigest(c *check.C) {
60 60
 	testRequires(c, DaemonIsLinux)
61 61
 	pushDigest, err := setupImage(c)
62 62
 	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
... ...
@@ -73,7 +74,15 @@ func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
73 73
 	c.Assert(pushDigest.String(), checker.Equals, pullDigest)
74 74
 }
75 75
 
76
-func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
76
+func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
77
+	testPullByTagDisplaysDigest(c)
78
+}
79
+
80
+func (s *DockerSchema1RegistrySuite) TestPullByTagDisplaysDigest(c *check.C) {
81
+	testPullByTagDisplaysDigest(c)
82
+}
83
+
84
+func testPullByDigest(c *check.C) {
77 85
 	testRequires(c, DaemonIsLinux)
78 86
 	pushDigest, err := setupImage(c)
79 87
 	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
... ...
@@ -91,7 +100,15 @@ func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
91 91
 	c.Assert(pushDigest.String(), checker.Equals, pullDigest)
92 92
 }
93 93
 
94
-func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
94
+func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) {
95
+	testPullByDigest(c)
96
+}
97
+
98
+func (s *DockerSchema1RegistrySuite) TestPullByDigest(c *check.C) {
99
+	testPullByDigest(c)
100
+}
101
+
102
+func testPullByDigestNoFallback(c *check.C) {
95 103
 	testRequires(c, DaemonIsLinux)
96 104
 	// pull from the registry using the <name>@<digest> reference
97 105
 	imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName)
... ...
@@ -100,6 +117,14 @@ func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
100 100
 	c.Assert(out, checker.Contains, "manifest unknown", check.Commentf("expected non-zero exit status and correct error message when pulling non-existing image"))
101 101
 }
102 102
 
103
+func (s *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
104
+	testPullByDigestNoFallback(c)
105
+}
106
+
107
+func (s *DockerSchema1RegistrySuite) TestPullByDigestNoFallback(c *check.C) {
108
+	testPullByDigestNoFallback(c)
109
+}
110
+
103 111
 func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) {
104 112
 	pushDigest, err := setupImage(c)
105 113
 	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
... ...
@@ -372,6 +397,7 @@ func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C)
372 372
 
373 373
 // TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
374 374
 // we have modified a manifest blob and its digest cannot be verified.
375
+// This is the schema2 version of the test.
375 376
 func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
376 377
 	testRequires(c, DaemonIsLinux)
377 378
 	manifestDigest, err := setupImage(c)
... ...
@@ -380,6 +406,46 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
380 380
 	// Load the target manifest blob.
381 381
 	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
382 382
 
383
+	var imgManifest schema2.Manifest
384
+	err = json.Unmarshal(manifestBlob, &imgManifest)
385
+	c.Assert(err, checker.IsNil, check.Commentf("unable to decode image manifest from blob"))
386
+
387
+	// Change a layer in the manifest.
388
+	imgManifest.Layers[0].Digest = digest.Digest("sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
389
+
390
+	// Move the existing data file aside, so that we can replace it with a
391
+	// malicious blob of data. NOTE: we defer the returned undo func.
392
+	undo := s.reg.tempMoveBlobData(c, manifestDigest)
393
+	defer undo()
394
+
395
+	alteredManifestBlob, err := json.MarshalIndent(imgManifest, "", "   ")
396
+	c.Assert(err, checker.IsNil, check.Commentf("unable to encode altered image manifest to JSON"))
397
+
398
+	s.reg.writeBlobContents(c, manifestDigest, alteredManifestBlob)
399
+
400
+	// Now try pulling that image by digest. We should get an error about
401
+	// digest verification for the manifest digest.
402
+
403
+	// Pull from the registry using the <name>@<digest> reference.
404
+	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
405
+	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
406
+	c.Assert(exitStatus, checker.Not(check.Equals), 0)
407
+
408
+	expectedErrorMsg := fmt.Sprintf("manifest verification failed for digest %s", manifestDigest)
409
+	c.Assert(out, checker.Contains, expectedErrorMsg)
410
+}
411
+
412
+// TestPullFailsWithAlteredManifest tests that a `docker pull` fails when
413
+// we have modified a manifest blob and its digest cannot be verified.
414
+// This is the schema1 version of the test.
415
+func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
416
+	testRequires(c, DaemonIsLinux)
417
+	manifestDigest, err := setupImage(c)
418
+	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
419
+
420
+	// Load the target manifest blob.
421
+	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
422
+
383 423
 	var imgManifest schema1.Manifest
384 424
 	err = json.Unmarshal(manifestBlob, &imgManifest)
385 425
 	c.Assert(err, checker.IsNil, check.Commentf("unable to decode image manifest from blob"))
... ...
@@ -413,6 +479,7 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredManifest(c *check.C) {
413 413
 
414 414
 // TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
415 415
 // we have modified a layer blob and its digest cannot be verified.
416
+// This is the schema2 version of the test.
416 417
 func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
417 418
 	testRequires(c, DaemonIsLinux)
418 419
 	manifestDigest, err := setupImage(c)
... ...
@@ -421,6 +488,49 @@ func (s *DockerRegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
421 421
 	// Load the target manifest blob.
422 422
 	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
423 423
 
424
+	var imgManifest schema2.Manifest
425
+	err = json.Unmarshal(manifestBlob, &imgManifest)
426
+	c.Assert(err, checker.IsNil)
427
+
428
+	// Next, get the digest of one of the layers from the manifest.
429
+	targetLayerDigest := imgManifest.Layers[0].Digest
430
+
431
+	// Move the existing data file aside, so that we can replace it with a
432
+	// malicious blob of data. NOTE: we defer the returned undo func.
433
+	undo := s.reg.tempMoveBlobData(c, targetLayerDigest)
434
+	defer undo()
435
+
436
+	// Now make a fake data blob in this directory.
437
+	s.reg.writeBlobContents(c, targetLayerDigest, []byte("This is not the data you are looking for."))
438
+
439
+	// Now try pulling that image by digest. We should get an error about
440
+	// digest verification for the target layer digest.
441
+
442
+	// Remove distribution cache to force a re-pull of the blobs
443
+	if err := os.RemoveAll(filepath.Join(dockerBasePath, "image", s.d.storageDriver, "distribution")); err != nil {
444
+		c.Fatalf("error clearing distribution cache: %v", err)
445
+	}
446
+
447
+	// Pull from the registry using the <name>@<digest> reference.
448
+	imageReference := fmt.Sprintf("%s@%s", repoName, manifestDigest)
449
+	out, exitStatus, _ := dockerCmdWithError("pull", imageReference)
450
+	c.Assert(exitStatus, checker.Not(check.Equals), 0, check.Commentf("expected a zero exit status"))
451
+
452
+	expectedErrorMsg := fmt.Sprintf("filesystem layer verification failed for digest %s", targetLayerDigest)
453
+	c.Assert(out, checker.Contains, expectedErrorMsg, check.Commentf("expected error message in output: %s", out))
454
+}
455
+
456
+// TestPullFailsWithAlteredLayer tests that a `docker pull` fails when
457
+// we have modified a layer blob and its digest cannot be verified.
458
+// This is the schema1 version of the test.
459
+func (s *DockerSchema1RegistrySuite) TestPullFailsWithAlteredLayer(c *check.C) {
460
+	testRequires(c, DaemonIsLinux)
461
+	manifestDigest, err := setupImage(c)
462
+	c.Assert(err, checker.IsNil)
463
+
464
+	// Load the target manifest blob.
465
+	manifestBlob := s.reg.readBlobContents(c, manifestDigest)
466
+
424 467
 	var imgManifest schema1.Manifest
425 468
 	err = json.Unmarshal(manifestBlob, &imgManifest)
426 469
 	c.Assert(err, checker.IsNil)
... ...
@@ -1,19 +1,29 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"fmt"
6
+	"io/ioutil"
7
+	"os"
5 8
 	"os/exec"
9
+	"path/filepath"
10
+	"runtime"
6 11
 	"strings"
7 12
 
13
+	"github.com/docker/distribution"
14
+	"github.com/docker/distribution/digest"
15
+	"github.com/docker/distribution/manifest"
16
+	"github.com/docker/distribution/manifest/manifestlist"
17
+	"github.com/docker/distribution/manifest/schema2"
8 18
 	"github.com/docker/docker/pkg/integration/checker"
9 19
 	"github.com/go-check/check"
10 20
 )
11 21
 
12
-// TestPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other
22
+// testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other
13 23
 // tags for the same image) are not also pulled down.
14 24
 //
15 25
 // Ref: docker/docker#8141
16
-func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
26
+func testPullImageWithAliases(c *check.C) {
17 27
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
18 28
 
19 29
 	repos := []string{}
... ...
@@ -40,8 +50,16 @@ func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
40 40
 	}
41 41
 }
42 42
 
43
-// TestConcurrentPullWholeRepo pulls the same repo concurrently.
44
-func (s *DockerRegistrySuite) TestConcurrentPullWholeRepo(c *check.C) {
43
+func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
44
+	testPullImageWithAliases(c)
45
+}
46
+
47
+func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) {
48
+	testPullImageWithAliases(c)
49
+}
50
+
51
+// testConcurrentPullWholeRepo pulls the same repo concurrently.
52
+func testConcurrentPullWholeRepo(c *check.C) {
45 53
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
46 54
 
47 55
 	repos := []string{}
... ...
@@ -89,8 +107,16 @@ func (s *DockerRegistrySuite) TestConcurrentPullWholeRepo(c *check.C) {
89 89
 	}
90 90
 }
91 91
 
92
-// TestConcurrentFailingPull tries a concurrent pull that doesn't succeed.
93
-func (s *DockerRegistrySuite) TestConcurrentFailingPull(c *check.C) {
92
+func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
93
+	testConcurrentPullWholeRepo(c)
94
+}
95
+
96
+func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) {
97
+	testConcurrentPullWholeRepo(c)
98
+}
99
+
100
+// testConcurrentFailingPull tries a concurrent pull that doesn't succeed.
101
+func testConcurrentFailingPull(c *check.C) {
94 102
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
95 103
 
96 104
 	// Run multiple pulls concurrently
... ...
@@ -112,9 +138,17 @@ func (s *DockerRegistrySuite) TestConcurrentFailingPull(c *check.C) {
112 112
 	}
113 113
 }
114 114
 
115
-// TestConcurrentPullMultipleTags pulls multiple tags from the same repo
115
+func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) {
116
+	testConcurrentFailingPull(c)
117
+}
118
+
119
+func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) {
120
+	testConcurrentFailingPull(c)
121
+}
122
+
123
+// testConcurrentPullMultipleTags pulls multiple tags from the same repo
116 124
 // concurrently.
117
-func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
125
+func testConcurrentPullMultipleTags(c *check.C) {
118 126
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
119 127
 
120 128
 	repos := []string{}
... ...
@@ -161,9 +195,17 @@ func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
161 161
 	}
162 162
 }
163 163
 
164
-// TestPullIDStability verifies that pushing an image and pulling it back
164
+func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
165
+	testConcurrentPullMultipleTags(c)
166
+}
167
+
168
+func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) {
169
+	testConcurrentPullMultipleTags(c)
170
+}
171
+
172
+// testPullIDStability verifies that pushing an image and pulling it back
165 173
 // preserves the image ID.
166
-func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
174
+func testPullIDStability(c *check.C) {
167 175
 	derivedImage := privateRegistryURL + "/dockercli/id-stability"
168 176
 	baseImage := "busybox"
169 177
 
... ...
@@ -229,6 +271,14 @@ func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
229 229
 	}
230 230
 }
231 231
 
232
+func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) {
233
+	testPullIDStability(c)
234
+}
235
+
236
+func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) {
237
+	testPullIDStability(c)
238
+}
239
+
232 240
 // TestPullFallbackOn404 tries to pull a nonexistent manifest and confirms that
233 241
 // the pull falls back to the v1 protocol.
234 242
 //
... ...
@@ -240,3 +290,85 @@ func (s *DockerRegistrySuite) TestPullFallbackOn404(c *check.C) {
240 240
 
241 241
 	c.Assert(out, checker.Contains, "v1 ping attempt")
242 242
 }
243
+
244
+func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) {
245
+	pushDigest, err := setupImage(c)
246
+	c.Assert(err, checker.IsNil, check.Commentf("error setting up image"))
247
+
248
+	// Inject a manifest list into the registry
249
+	manifestList := &manifestlist.ManifestList{
250
+		Versioned: manifest.Versioned{
251
+			SchemaVersion: 2,
252
+			MediaType:     manifestlist.MediaTypeManifestList,
253
+		},
254
+		Manifests: []manifestlist.ManifestDescriptor{
255
+			{
256
+				Descriptor: distribution.Descriptor{
257
+					Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
258
+					Size:      3253,
259
+					MediaType: schema2.MediaTypeManifest,
260
+				},
261
+				Platform: manifestlist.PlatformSpec{
262
+					Architecture: "bogus_arch",
263
+					OS:           "bogus_os",
264
+				},
265
+			},
266
+			{
267
+				Descriptor: distribution.Descriptor{
268
+					Digest:    pushDigest,
269
+					Size:      3253,
270
+					MediaType: schema2.MediaTypeManifest,
271
+				},
272
+				Platform: manifestlist.PlatformSpec{
273
+					Architecture: runtime.GOARCH,
274
+					OS:           runtime.GOOS,
275
+				},
276
+			},
277
+		},
278
+	}
279
+
280
+	manifestListJSON, err := json.MarshalIndent(manifestList, "", "   ")
281
+	c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list"))
282
+
283
+	manifestListDigest := digest.FromBytes(manifestListJSON)
284
+	hexDigest := manifestListDigest.Hex()
285
+
286
+	registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2")
287
+
288
+	// Write manifest list to blob store
289
+	blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest)
290
+	err = os.MkdirAll(blobDir, 0755)
291
+	c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir"))
292
+	blobPath := filepath.Join(blobDir, "data")
293
+	err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644)
294
+	c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list"))
295
+
296
+	// Add to revision store
297
+	revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest)
298
+	err = os.Mkdir(revisionDir, 0755)
299
+	c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir"))
300
+	revisionPath := filepath.Join(revisionDir, "link")
301
+	err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644)
302
+	c.Assert(err, checker.IsNil, check.Commentf("error writing revision link"))
303
+
304
+	// Update tag
305
+	tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link")
306
+	err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644)
307
+	c.Assert(err, checker.IsNil, check.Commentf("error writing tag link"))
308
+
309
+	// Verify that the image can be pulled through the manifest list.
310
+	out, _ := dockerCmd(c, "pull", repoName)
311
+
312
+	// The pull output includes "Digest: <digest>", so find that
313
+	matches := digestRegex.FindStringSubmatch(out)
314
+	c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out))
315
+	pullDigest := matches[1]
316
+
317
+	// Make sure the pushed and pull digests match
318
+	c.Assert(manifestListDigest.String(), checker.Equals, pullDigest)
319
+
320
+	// Was the image actually created?
321
+	dockerCmd(c, "inspect", repoName)
322
+
323
+	dockerCmd(c, "rmi", repoName)
324
+}
... ...
@@ -16,7 +16,7 @@ import (
16 16
 )
17 17
 
18 18
 // Pushing an image to a private registry.
19
-func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) {
19
+func testPushBusyboxImage(c *check.C) {
20 20
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
21 21
 	// tag the image to upload it to the private registry
22 22
 	dockerCmd(c, "tag", "busybox", repoName)
... ...
@@ -24,13 +24,21 @@ func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) {
24 24
 	dockerCmd(c, "push", repoName)
25 25
 }
26 26
 
27
+func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) {
28
+	testPushBusyboxImage(c)
29
+}
30
+
31
+func (s *DockerSchema1RegistrySuite) TestPushBusyboxImage(c *check.C) {
32
+	testPushBusyboxImage(c)
33
+}
34
+
27 35
 // pushing an image without a prefix should throw an error
28 36
 func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) {
29 37
 	out, _, err := dockerCmdWithError("push", "busybox")
30 38
 	c.Assert(err, check.NotNil, check.Commentf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out))
31 39
 }
32 40
 
33
-func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) {
41
+func testPushUntagged(c *check.C) {
34 42
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
35 43
 	expected := "Repository does not exist"
36 44
 
... ...
@@ -39,7 +47,15 @@ func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) {
39 39
 	c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed"))
40 40
 }
41 41
 
42
-func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) {
42
+func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) {
43
+	testPushUntagged(c)
44
+}
45
+
46
+func (s *DockerSchema1RegistrySuite) TestPushUntagged(c *check.C) {
47
+	testPushUntagged(c)
48
+}
49
+
50
+func testPushBadTag(c *check.C) {
43 51
 	repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL)
44 52
 	expected := "does not exist"
45 53
 
... ...
@@ -48,7 +64,15 @@ func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) {
48 48
 	c.Assert(out, checker.Contains, expected, check.Commentf("pushing the image failed"))
49 49
 }
50 50
 
51
-func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
51
+func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) {
52
+	testPushBadTag(c)
53
+}
54
+
55
+func (s *DockerSchema1RegistrySuite) TestPushBadTag(c *check.C) {
56
+	testPushBadTag(c)
57
+}
58
+
59
+func testPushMultipleTags(c *check.C) {
52 60
 	repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
53 61
 	repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL)
54 62
 	repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL)
... ...
@@ -85,7 +109,15 @@ func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
85 85
 	}
86 86
 }
87 87
 
88
-func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
88
+func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) {
89
+	testPushMultipleTags(c)
90
+}
91
+
92
+func (s *DockerSchema1RegistrySuite) TestPushMultipleTags(c *check.C) {
93
+	testPushMultipleTags(c)
94
+}
95
+
96
+func testPushEmptyLayer(c *check.C) {
89 97
 	repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL)
90 98
 	emptyTarball, err := ioutil.TempFile("", "empty_tarball")
91 99
 	c.Assert(err, check.IsNil, check.Commentf("Unable to create test file"))
... ...
@@ -107,6 +139,14 @@ func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
107 107
 	c.Assert(err, check.IsNil, check.Commentf("pushing the image to the private registry has failed: %s", out))
108 108
 }
109 109
 
110
+func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) {
111
+	testPushEmptyLayer(c)
112
+}
113
+
114
+func (s *DockerSchema1RegistrySuite) TestPushEmptyLayer(c *check.C) {
115
+	testPushEmptyLayer(c)
116
+}
117
+
110 118
 func (s *DockerTrustSuite) TestTrustedPush(c *check.C) {
111 119
 	repoName := fmt.Sprintf("%v/dockercli/trusted:latest", privateRegistryURL)
112 120
 	// tag the image and upload it to the private registry
... ...
@@ -1554,9 +1554,9 @@ func daemonTime(c *check.C) time.Time {
1554 1554
 	return dt
1555 1555
 }
1556 1556
 
1557
-func setupRegistry(c *check.C) *testRegistryV2 {
1557
+func setupRegistry(c *check.C, schema1 bool) *testRegistryV2 {
1558 1558
 	testRequires(c, RegistryHosting)
1559
-	reg, err := newTestRegistryV2(c)
1559
+	reg, err := newTestRegistryV2(c, schema1)
1560 1560
 	c.Assert(err, check.IsNil)
1561 1561
 
1562 1562
 	// Wait for registry to be ready to serve requests.
... ...
@@ -12,14 +12,17 @@ import (
12 12
 	"github.com/go-check/check"
13 13
 )
14 14
 
15
-const v2binary = "registry-v2"
15
+const (
16
+	v2binary        = "registry-v2"
17
+	v2binarySchema1 = "registry-v2-schema1"
18
+)
16 19
 
17 20
 type testRegistryV2 struct {
18 21
 	cmd *exec.Cmd
19 22
 	dir string
20 23
 }
21 24
 
22
-func newTestRegistryV2(c *check.C) (*testRegistryV2, error) {
25
+func newTestRegistryV2(c *check.C, schema1 bool) (*testRegistryV2, error) {
23 26
 	template := `version: 0.1
24 27
 loglevel: debug
25 28
 storage:
... ...
@@ -41,7 +44,11 @@ http:
41 41
 		return nil, err
42 42
 	}
43 43
 
44
-	cmd := exec.Command(v2binary, confPath)
44
+	binary := v2binary
45
+	if schema1 {
46
+		binary = v2binarySchema1
47
+	}
48
+	cmd := exec.Command(binary, confPath)
45 49
 	if err := cmd.Start(); err != nil {
46 50
 		os.RemoveAll(tmp)
47 51
 		if os.IsNotExist(err) {
... ...
@@ -15,12 +15,8 @@ import (
15 15
 
16 16
 func randomLayerID(seed int64) ChainID {
17 17
 	r := rand.New(rand.NewSource(seed))
18
-	dgst, err := digest.FromBytes([]byte(fmt.Sprintf("%d", r.Int63())))
19
-	if err != nil {
20
-		panic(err)
21
-	}
22 18
 
23
-	return ChainID(dgst)
19
+	return ChainID(digest.FromBytes([]byte(fmt.Sprintf("%d", r.Int63()))))
24 20
 }
25 21
 
26 22
 func newFileMetadataStore(t *testing.T) (*fileMetadataStore, string, func()) {
... ...
@@ -233,12 +233,7 @@ func createChainIDFromParent(parent ChainID, dgsts ...DiffID) ChainID {
233 233
 		return createChainIDFromParent(ChainID(dgsts[0]), dgsts[1:]...)
234 234
 	}
235 235
 	// H = "H(n-1) SHA256(n)"
236
-	dgst, err := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
237
-	if err != nil {
238
-		// Digest calculation is not expected to throw an error,
239
-		// any error at this point is a program error
240
-		panic(err)
241
-	}
236
+	dgst := digest.FromBytes([]byte(string(parent) + " " + string(dgsts[0])))
242 237
 	return createChainIDFromParent(ChainID(dgst), dgsts[1:]...)
243 238
 }
244 239
 
... ...
@@ -548,10 +548,7 @@ func TestTarStreamStability(t *testing.T) {
548 548
 }
549 549
 
550 550
 func assertLayerDiff(t *testing.T, expected []byte, layer Layer) {
551
-	expectedDigest, err := digest.FromBytes(expected)
552
-	if err != nil {
553
-		t.Fatal(err)
554
-	}
551
+	expectedDigest := digest.FromBytes(expected)
555 552
 
556 553
 	if digest.Digest(layer.DiffID()) != expectedDigest {
557 554
 		t.Fatalf("Mismatched diff id for %s, got %s, expected %s", layer.ChainID(), layer.DiffID(), expected)
... ...
@@ -573,10 +570,7 @@ func assertLayerDiff(t *testing.T, expected []byte, layer Layer) {
573 573
 		t.Fatalf("Mismatched tar stream size for %s, got %d, expected %d", layer.ChainID(), len(actual), len(expected))
574 574
 	}
575 575
 
576
-	actualDigest, err := digest.FromBytes(actual)
577
-	if err != nil {
578
-		t.Fatal(err)
579
-	}
576
+	actualDigest := digest.FromBytes(actual)
580 577
 
581 578
 	if actualDigest != expectedDigest {
582 579
 		logByteDiff(t, actual, expected)
... ...
@@ -37,10 +37,7 @@ func GetLayerPath(s Store, layer ChainID) (string, error) {
37 37
 
38 38
 func (ls *layerStore) RegisterDiffID(graphID string, size int64) (Layer, error) {
39 39
 	var err error // this is used for cleanup in existingLayer case
40
-	diffID, err := digest.FromBytes([]byte(graphID))
41
-	if err != nil {
42
-		return nil, err
43
-	}
40
+	diffID := digest.FromBytes([]byte(graphID))
44 41
 
45 42
 	// Create new roLayer
46 43
 	layer := &roLayer{
... ...
@@ -5,3 +5,10 @@ Brian Bland <brian.bland@docker.com>    Brian Bland <r4nd0m1n4t0r@gmail.com>
5 5
 Josh Hawn <josh.hawn@docker.com>        Josh Hawn <jlhawn@berkeley.edu>
6 6
 Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com>
7 7
 Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com>
8
+Andrew Meredith <andymeredith@gmail.com> Andrew Meredith <kendru@users.noreply.github.com>
9
+harche <p.harshal@gmail.com> harche <harche@users.noreply.github.com>
10
+Jessie Frazelle <jessie@docker.com>  <jfrazelle@users.noreply.github.com>
11
+Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com>
12
+Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com>
13
+Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr>
14
+davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com>
8 15
\ No newline at end of file
... ...
@@ -5,13 +5,16 @@ Adrian Mouat <adrian.mouat@gmail.com>
5 5
 Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>
6 6
 Alex Chan <alex.chan@metaswitch.com>
7 7
 Alex Elman <aelman@indeed.com>
8
+amitshukla <ashukla73@hotmail.com>
8 9
 Amy Lindburg <amy.lindburg@docker.com>
10
+Andrew Meredith <andymeredith@gmail.com>
9 11
 Andrey Kostov <kostov.andrey@gmail.com>
10 12
 Andy Goldstein <agoldste@redhat.com>
11 13
 Anton Tiurin <noxiouz@yandex.ru>
12 14
 Antonio Mercado <amercado@thinknode.com>
13 15
 Arnaud Porterie <arnaud.porterie@docker.com>
14 16
 Arthur Baars <arthur@semmle.com>
17
+Avi Miller <avi.miller@oracle.com>
15 18
 Ayose Cazorla <ayosec@gmail.com>
16 19
 BadZen <dave.trombley@gmail.com>
17 20
 Ben Firshman <ben@firshman.co.uk>
... ...
@@ -32,9 +35,10 @@ Derek McGowan <derek@mcgstyle.net>
32 32
 Diogo Mónica <diogo.monica@gmail.com>
33 33
 Donald Huang <don.hcd@gmail.com>
34 34
 Doug Davis <dug@us.ibm.com>
35
+farmerworking <farmerworking@gmail.com>
35 36
 Florentin Raud <florentin.raud@gmail.com>
36 37
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
37
-harche <harche@users.noreply.github.com>
38
+harche <p.harshal@gmail.com>
38 39
 Henri Gomez <henri.gomez@gmail.com>
39 40
 Hu Keping <hukeping@huawei.com>
40 41
 Hua Wang <wanghua.humble@gmail.com>
... ...
@@ -42,9 +46,10 @@ Ian Babrou <ibobrik@gmail.com>
42 42
 Jack Griffin <jackpg14@gmail.com>
43 43
 Jason Freidman <jason.freidman@gmail.com>
44 44
 Jeff Nickoloff <jeff@allingeek.com>
45
-Jessie Frazelle <jfrazelle@users.noreply.github.com>
45
+Jessie Frazelle <jessie@docker.com>
46 46
 Jianqing Wang <tsing@jianqing.org>
47 47
 Jon Poler <jonathan.poler@apcera.com>
48
+Jonathan Boulle <jonathanboulle@gmail.com>
48 49
 Jordan Liggitt <jliggitt@redhat.com>
49 50
 Josh Hawn <josh.hawn@docker.com>
50 51
 Julien Fernandez <julien.fernandez@gmail.com>
... ...
@@ -59,6 +64,7 @@ Matt Moore <mattmoor@google.com>
59 59
 Matt Robenolt <matt@ydekproductions.com>
60 60
 Michael Prokop <mika@grml.org>
61 61
 Miquel Sabaté <msabate@suse.com>
62
+Morgan Bauer <mbauer@us.ibm.com>
62 63
 moxiegirl <mary@docker.com>
63 64
 Nathan Sullivan <nathan@nightsys.net>
64 65
 nevermosby <robolwq@qq.com>
... ...
@@ -70,8 +76,8 @@ Olivier Jacques <olivier.jacques@hp.com>
70 70
 Patrick Devine <patrick.devine@docker.com>
71 71
 Philip Misiowiec <philip@atlashealth.com>
72 72
 Richard Scothern <richard.scothern@docker.com>
73
+Rusty Conover <rusty@luckydinosaur.com>
73 74
 Sebastiaan van Stijn <github@gone.nl>
74
-Sharif Nassar <mrwacky42@users.noreply.github.com>
75 75
 Sharif Nassar <sharif@mrwacky.com>
76 76
 Shawn Falkner-Horine <dreadpirateshawn@gmail.com>
77 77
 Shreyas Karnik <karnik.shreyas@gmail.com>
... ...
@@ -81,15 +87,16 @@ Stephen J Day <stephen.day@docker.com>
81 81
 Sungho Moon <sungho.moon@navercorp.com>
82 82
 Sven Dowideit <SvenDowideit@home.org.au>
83 83
 Sylvain Baubeau <sbaubeau@redhat.com>
84
+Ted Reed <ted.reed@gmail.com>
84 85
 tgic <farmer1992@gmail.com>
85 86
 Thomas Sjögren <konstruktoid@users.noreply.github.com>
86 87
 Tianon Gravi <admwiggin@gmail.com>
87 88
 Tibor Vass <teabee89@gmail.com>
89
+Tonis Tiigi <tonistiigi@gmail.com>
88 90
 Troels Thomsen <troels@thomsen.io>
89 91
 Vincent Batts <vbatts@redhat.com>
90 92
 Vincent Demeester <vincent@sbr.pm>
91 93
 Vincent Giersch <vincent.giersch@ovh.net>
92
-Vincent Giersch <vincent@giersch.fr>
93 94
 W. Trevor King <wking@tremily.us>
94 95
 xg.song <xg.song@venusource.com>
95 96
 xiekeyang <xiekeyang@huawei.com>
... ...
@@ -1,4 +1,4 @@
1
-FROM golang:1.4
1
+FROM golang:1.5.2
2 2
 
3 3
 RUN apt-get update && \
4 4
     apt-get install -y librados-dev apache2-utils && \
... ...
@@ -1,8 +1,58 @@
1
-Solomon Hykes <solomon@docker.com> (@shykes)
2
-Olivier Gambier <olivier@docker.com> (@dmp42)
3
-Stephen Day <stephen.day@docker.com> (@stevvooe)
4
-Derek McGowan <derek@mcgstyle.net> (@dmcgowan)
5
-Richard Scothern <richard.scothern@gmail.com> (@richardscothern)
6
-Aaron Lehmann <aaron.lehmann@docker.com> (@aaronlehmann)
1
+# Distribution maintainers file
2
+#
3
+# This file describes who runs the docker/distribution project and how.
4
+# This is a living document - if you see something out of date or missing, speak up!
5
+#
6
+# It is structured to be consumable by both humans and programs.
7
+# To extract its contents programmatically, use any TOML-compliant parser.
8
+#
9
+# This file is compiled into the MAINTAINERS file in docker/opensource.
10
+#
11
+[Org]
12
+	[Org."Core maintainers"]
13
+		people = [
14
+			"aaronlehmann",
15
+			"dmcgowan",
16
+			"dmp42",
17
+			"richardscothern",
18
+			"shykes",
19
+			"stevvooe",
20
+		]
7 21
 
22
+[people]
8 23
 
24
+# A reference list of all people associated with the project.
25
+# All other sections should refer to people by their canonical key
26
+# in the people section.
27
+
28
+	# ADD YOURSELF HERE IN ALPHABETICAL ORDER
29
+
30
+	[people.aaronlehmann]
31
+	Name = "Aaron Lehmann"
32
+	Email = "aaron.lehmann@docker.com"
33
+	GitHub = "aaronlehmann"
34
+
35
+	[people.dmcgowan]
36
+	Name = "Derek McGowan"
37
+	Email = "derek@mcgstyle.net"
38
+	GitHub = "dmcgowan"
39
+
40
+	[people.dmp42]
41
+	Name = "Olivier Gambier"
42
+	Email = "olivier@docker.com"
43
+	GitHub = "dmp42"
44
+
45
+	[people.richardscothern]
46
+	Name = "Richard Scothern"
47
+	Email = "richard.scothern@gmail.com"
48
+	GitHub = "richardscothern"
49
+
50
+	[people.shykes]
51
+	Name = "Solomon Hykes"
52
+	Email = "solomon@docker.com"
53
+	GitHub = "shykes"
54
+
55
+	[people.stevvooe]
56
+	Name = "Stephen Day"
57
+	Email = "stephen.day@docker.com"
58
+	GitHub = "stevvooe"
... ...
@@ -17,9 +17,9 @@ This repository contains the following components:
17 17
 |**Component**       |Description                                                                                                                                                                                         |
18 18
 |--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
19 19
 | **registry**       | An implementation of the [Docker Registry HTTP API V2](docs/spec/api.md) for use with docker 1.6+.                                                                                                  |
20
-| **libraries**      | A rich set of libraries for interacting with,distribution components. Please see [godoc](http://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
20
+| **libraries**      | A rich set of libraries for interacting with,distribution components. Please see [godoc](https://godoc.org/github.com/docker/distribution) for details. **Note**: These libraries are **unstable**. |
21 21
 | **specifications** | _Distribution_ related specifications are available in [docs/spec](docs/spec)                                                                                                                        |
22
-| **documentation**  | Docker's full documentation set is available at [docs.docker.com](http://docs.docker.com). This repository [contains the subset](docs/index.md) related just to the registry.                                                                                                                                          |
22
+| **documentation**  | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/index.md) related just to the registry.                                                                                                                                          |
23 23
 
24 24
 ### How does this integrate with Docker engine?
25 25
 
... ...
@@ -58,7 +58,7 @@ For information on upcoming functionality, please see [ROADMAP.md](ROADMAP.md).
58 58
 ### Who needs to deploy a registry?
59 59
 
60 60
 By default, Docker users pull images from Docker's public registry instance.
61
-[Installing Docker](http://docs.docker.com/installation) gives users this
61
+[Installing Docker](https://docs.docker.com/engine/installation/) gives users this
62 62
 ability. Users can also push images to a repository on Docker's public registry,
63 63
 if they have a [Docker Hub](https://hub.docker.com/) account. 
64 64
 
... ...
@@ -61,6 +61,15 @@ type Descriptor struct {
61 61
 	// depend on the simplicity of this type.
62 62
 }
63 63
 
64
+// Descriptor returns the descriptor, to make it satisfy the Describable
65
+// interface. Note that implementations of Describable are generally objects
66
+// which can be described, not simply descriptors; this exception is in place
67
+// to make it more convenient to pass actual descriptors to functions that
68
+// expect Describable objects.
69
+func (d Descriptor) Descriptor() Descriptor {
70
+	return d
71
+}
72
+
64 73
 // BlobStatter makes blob descriptors available by digest. The service may
65 74
 // provide a descriptor of a different digest if the provided digest is not
66 75
 // canonical.
... ...
@@ -6,6 +6,8 @@ machine:
6 6
   # Install ceph to test rados driver & create pool
7 7
     - sudo -i ~/distribution/contrib/ceph/ci-setup.sh
8 8
     - ceph osd pool create docker-distribution 1
9
+  # Install codecov for coverage
10
+    - pip install --user codecov
9 11
 
10 12
   post:
11 13
   # go
... ...
@@ -45,9 +47,6 @@ dependencies:
45 45
     - >
46 46
       gvm use stable &&
47 47
       go get github.com/axw/gocov/gocov github.com/golang/lint/golint
48
- 
49
-  # Disabling goveralls for now
50
-  # go get github.com/axw/gocov/gocov github.com/mattn/goveralls github.com/golang/lint/golint
51 48
 
52 49
 test:
53 50
   pre:
... ...
@@ -73,25 +72,17 @@ test:
73 73
         pwd: $BASE_STABLE
74 74
 
75 75
   override:
76
-
77 76
   # Test stable, and report
78
-  # Preset the goverall report file
79
-  # - echo "$CIRCLE_PAIN" > ~/goverage.report
80
-
81
-     - gvm use stable; go list ./... | xargs -L 1 -I{} rm -f $GOPATH/src/{}/coverage.out:
82
-         pwd: $BASE_STABLE
83
-
84
-     - gvm use stable; go list -tags "$DOCKER_BUILDTAGS" ./... | xargs -L 1 -I{} godep go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/{}/coverage.out {}:
77
+     - gvm use stable; export ROOT_PACKAGE=$(go list .); go list -tags "$DOCKER_BUILDTAGS" ./... | xargs -L 1 -I{} bash -c 'export PACKAGE={}; godep go test -tags "$DOCKER_BUILDTAGS" -test.short -coverprofile=$GOPATH/src/$PACKAGE/coverage.out -coverpkg=$(./coverpkg.sh $PACKAGE $ROOT_PACKAGE) $PACKAGE':
85 78
          timeout: 600
86 79
          pwd: $BASE_STABLE
87 80
 
88 81
   post:
89
-  # Aggregate and report to coveralls
90
-    - gvm use stable; go list -tags "$DOCKER_BUILDTAGS" ./... | xargs -L 1 -I{} cat "$GOPATH/src/{}/coverage.out" | grep -v "$CIRCLE_PAIN" >> ~/goverage.report:
82
+  # Report to codecov
83
+    - bash <(curl -s https://codecov.io/bash):
91 84
         pwd: $BASE_STABLE
92 85
 
93 86
   ## Notes
94
-  # Disabled coveralls reporting: build breaking sending coverage data to coveralls
95 87
   # Disabled the -race detector due to massive memory usage.
96 88
   # Do we want these as well?
97 89
   # - go get code.google.com/p/go.tools/cmd/goimports
98 90
new file mode 100755
... ...
@@ -0,0 +1,7 @@
0
+#!/usr/bin/env bash
1
+# Given a subpackage and the containing package, figures out which packages
2
+# need to be passed to `go test -coverpkg`:  this includes all of the
3
+# subpackage's dependencies within the containing package, as well as the
4
+# subpackage itself.
5
+DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2})"
6
+echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','
... ...
@@ -1,21 +1,14 @@
1 1
 package digest
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"fmt"
6 5
 	"hash"
7 6
 	"io"
8
-	"io/ioutil"
9 7
 	"regexp"
10 8
 	"strings"
11
-
12
-	"github.com/docker/docker/pkg/tarsum"
13 9
 )
14 10
 
15 11
 const (
16
-	// DigestTarSumV1EmptyTar is the digest for the empty tar file.
17
-	DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
18
-
19 12
 	// DigestSha256EmptyTar is the canonical sha256 digest of empty data
20 13
 	DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
21 14
 )
... ...
@@ -29,18 +22,21 @@ const (
29 29
 //
30 30
 // 	sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
31 31
 //
32
-// More important for this code base, this type is compatible with tarsum
33
-// digests. For example, the following would be a valid Digest:
34
-//
35
-// 	tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b
36
-//
37 32
 // This allows to abstract the digest behind this type and work only in those
38 33
 // terms.
39 34
 type Digest string
40 35
 
41 36
 // NewDigest returns a Digest from alg and a hash.Hash object.
42 37
 func NewDigest(alg Algorithm, h hash.Hash) Digest {
43
-	return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
38
+	return NewDigestFromBytes(alg, h.Sum(nil))
39
+}
40
+
41
+// NewDigestFromBytes returns a new digest from the byte contents of p.
42
+// Typically, this can come from hash.Hash.Sum(...) or xxx.SumXXX(...)
43
+// functions. This is also useful for rebuilding digests from binary
44
+// serializations.
45
+func NewDigestFromBytes(alg Algorithm, p []byte) Digest {
46
+	return Digest(fmt.Sprintf("%s:%x", alg, p))
44 47
 }
45 48
 
46 49
 // NewDigestFromHex returns a Digest from alg and a the hex encoded digest.
... ...
@@ -79,41 +75,15 @@ func FromReader(rd io.Reader) (Digest, error) {
79 79
 	return Canonical.FromReader(rd)
80 80
 }
81 81
 
82
-// FromTarArchive produces a tarsum digest from reader rd.
83
-func FromTarArchive(rd io.Reader) (Digest, error) {
84
-	ts, err := tarsum.NewTarSum(rd, true, tarsum.Version1)
85
-	if err != nil {
86
-		return "", err
87
-	}
88
-
89
-	if _, err := io.Copy(ioutil.Discard, ts); err != nil {
90
-		return "", err
91
-	}
92
-
93
-	d, err := ParseDigest(ts.Sum(nil))
94
-	if err != nil {
95
-		return "", err
96
-	}
97
-
98
-	return d, nil
99
-}
100
-
101 82
 // FromBytes digests the input and returns a Digest.
102
-func FromBytes(p []byte) (Digest, error) {
103
-	return FromReader(bytes.NewReader(p))
83
+func FromBytes(p []byte) Digest {
84
+	return Canonical.FromBytes(p)
104 85
 }
105 86
 
106 87
 // Validate checks that the contents of d is a valid digest, returning an
107 88
 // error if not.
108 89
 func (d Digest) Validate() error {
109 90
 	s := string(d)
110
-	// Common case will be tarsum
111
-	_, err := ParseTarSum(s)
112
-	if err == nil {
113
-		return nil
114
-	}
115
-
116
-	// Continue on for general parser
117 91
 
118 92
 	if !DigestRegexpAnchored.MatchString(s) {
119 93
 		return ErrDigestInvalidFormat
... ...
@@ -2,6 +2,7 @@ package digest
2 2
 
3 3
 import (
4 4
 	"crypto"
5
+	"fmt"
5 6
 	"hash"
6 7
 	"io"
7 8
 )
... ...
@@ -13,10 +14,9 @@ type Algorithm string
13 13
 
14 14
 // supported digest types
15 15
 const (
16
-	SHA256         Algorithm = "sha256"           // sha256 with hex encoding
17
-	SHA384         Algorithm = "sha384"           // sha384 with hex encoding
18
-	SHA512         Algorithm = "sha512"           // sha512 with hex encoding
19
-	TarsumV1SHA256 Algorithm = "tarsum+v1+sha256" // supported tarsum version, verification only
16
+	SHA256 Algorithm = "sha256" // sha256 with hex encoding
17
+	SHA384 Algorithm = "sha384" // sha384 with hex encoding
18
+	SHA512 Algorithm = "sha512" // sha512 with hex encoding
20 19
 
21 20
 	// Canonical is the primary digest algorithm used with the distribution
22 21
 	// project. Other digests may be used but this one is the primary storage
... ...
@@ -85,11 +85,18 @@ func (a Algorithm) New() Digester {
85 85
 	}
86 86
 }
87 87
 
88
-// Hash returns a new hash as used by the algorithm. If not available, nil is
89
-// returned. Make sure to check Available before calling.
88
+// Hash returns a new hash as used by the algorithm. If not available, the
89
+// method will panic. Check Algorithm.Available() before calling.
90 90
 func (a Algorithm) Hash() hash.Hash {
91 91
 	if !a.Available() {
92
-		return nil
92
+		// NOTE(stevvooe): A missing hash is usually a programming error that
93
+		// must be resolved at compile time. We don't import in the digest
94
+		// package to allow users to choose their hash implementation (such as
95
+		// when using stevvooe/resumable or a hardware accelerated package).
96
+		//
97
+		// Applications that may want to resolve the hash at runtime should
98
+		// call Algorithm.Available before call Algorithm.Hash().
99
+		panic(fmt.Sprintf("%v not available (make sure it is imported)", a))
93 100
 	}
94 101
 
95 102
 	return algorithms[a].New()
... ...
@@ -106,6 +113,22 @@ func (a Algorithm) FromReader(rd io.Reader) (Digest, error) {
106 106
 	return digester.Digest(), nil
107 107
 }
108 108
 
109
+// FromBytes digests the input and returns a Digest.
110
+func (a Algorithm) FromBytes(p []byte) Digest {
111
+	digester := a.New()
112
+
113
+	if _, err := digester.Hash().Write(p); err != nil {
114
+		// Writes to a Hash should never fail. None of the existing
115
+		// hash implementations in the stdlib or hashes vendored
116
+		// here can return errors from Write. Having a panic in this
117
+		// condition instead of having FromBytes return an error value
118
+		// avoids unnecessary error handling paths in all callers.
119
+		panic("write to hash function returned error: " + err.Error())
120
+	}
121
+
122
+	return digester.Digest()
123
+}
124
+
109 125
 // TODO(stevvooe): Allow resolution of verifiers using the digest type and
110 126
 // this registration system.
111 127
 
... ...
@@ -1,7 +1,7 @@
1 1
 // Package digest provides a generalized type to opaquely represent message
2 2
 // digests and their operations within the registry. The Digest type is
3 3
 // designed to serve as a flexible identifier in a content-addressable system.
4
-// More importantly, it provides tools and wrappers to work with tarsums and
4
+// More importantly, it provides tools and wrappers to work with
5 5
 // hash.Hash-based digests with little effort.
6 6
 //
7 7
 // Basics
... ...
@@ -16,17 +16,7 @@
16 16
 // 	sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc
17 17
 //
18 18
 // In this case, the string "sha256" is the algorithm and the hex bytes are
19
-// the "digest". A tarsum example will be more illustrative of the use case
20
-// involved in the registry:
21
-//
22
-// 	tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b
23
-//
24
-// For this, we consider the algorithm to be "tarsum+sha256". Prudent
25
-// applications will favor the ParseDigest function to verify the format over
26
-// using simple type casts. However, a normal string can be cast as a digest
27
-// with a simple type conversion:
28
-//
29
-// 	Digest("tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b")
19
+// the "digest".
30 20
 //
31 21
 // Because the Digest type is simply a string, once a valid Digest is
32 22
 // obtained, comparisons are cheap, quick and simple to express with the
33 23
deleted file mode 100644
... ...
@@ -1,70 +0,0 @@
1
-package digest
2
-
3
-import (
4
-	"fmt"
5
-
6
-	"regexp"
7
-)
8
-
9
-// TarsumRegexp defines a regular expression to match tarsum identifiers.
10
-var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+")
11
-
12
-// TarsumRegexpCapturing defines a regular expression to match tarsum identifiers with
13
-// capture groups corresponding to each component.
14
-var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)")
15
-
16
-// TarSumInfo contains information about a parsed tarsum.
17
-type TarSumInfo struct {
18
-	// Version contains the version of the tarsum.
19
-	Version string
20
-
21
-	// Algorithm contains the algorithm for the final digest
22
-	Algorithm string
23
-
24
-	// Digest contains the hex-encoded digest.
25
-	Digest string
26
-}
27
-
28
-// InvalidTarSumError provides informations about a TarSum that cannot be parsed
29
-// by ParseTarSum.
30
-type InvalidTarSumError string
31
-
32
-func (e InvalidTarSumError) Error() string {
33
-	return fmt.Sprintf("invalid tarsum: %q", string(e))
34
-}
35
-
36
-// ParseTarSum parses a tarsum string into its components of interest. For
37
-// example, this method may receive the tarsum in the following format:
38
-//
39
-//		tarsum.v1+sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e
40
-//
41
-// The function will return the following:
42
-//
43
-//		TarSumInfo{
44
-//			Version: "v1",
45
-//			Algorithm: "sha256",
46
-//			Digest: "220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e",
47
-//		}
48
-//
49
-func ParseTarSum(tarSum string) (tsi TarSumInfo, err error) {
50
-	components := TarsumRegexpCapturing.FindStringSubmatch(tarSum)
51
-
52
-	if len(components) != 1+TarsumRegexpCapturing.NumSubexp() {
53
-		return TarSumInfo{}, InvalidTarSumError(tarSum)
54
-	}
55
-
56
-	return TarSumInfo{
57
-		Version:   components[3],
58
-		Algorithm: components[4],
59
-		Digest:    components[5],
60
-	}, nil
61
-}
62
-
63
-// String returns the valid, string representation of the tarsum info.
64
-func (tsi TarSumInfo) String() string {
65
-	if tsi.Version == "" {
66
-		return fmt.Sprintf("tarsum+%s:%s", tsi.Algorithm, tsi.Digest)
67
-	}
68
-
69
-	return fmt.Sprintf("tarsum.%s+%s:%s", tsi.Version, tsi.Algorithm, tsi.Digest)
70
-}
... ...
@@ -3,9 +3,6 @@ package digest
3 3
 import (
4 4
 	"hash"
5 5
 	"io"
6
-	"io/ioutil"
7
-
8
-	"github.com/docker/docker/pkg/tarsum"
9 6
 )
10 7
 
11 8
 // Verifier presents a general verification interface to be used with message
... ...
@@ -27,70 +24,10 @@ func NewDigestVerifier(d Digest) (Verifier, error) {
27 27
 		return nil, err
28 28
 	}
29 29
 
30
-	alg := d.Algorithm()
31
-	switch alg {
32
-	case "sha256", "sha384", "sha512":
33
-		return hashVerifier{
34
-			hash:   alg.Hash(),
35
-			digest: d,
36
-		}, nil
37
-	default:
38
-		// Assume we have a tarsum.
39
-		version, err := tarsum.GetVersionFromTarsum(string(d))
40
-		if err != nil {
41
-			return nil, err
42
-		}
43
-
44
-		pr, pw := io.Pipe()
45
-
46
-		// TODO(stevvooe): We may actually want to ban the earlier versions of
47
-		// tarsum. That decision may not be the place of the verifier.
48
-
49
-		ts, err := tarsum.NewTarSum(pr, true, version)
50
-		if err != nil {
51
-			return nil, err
52
-		}
53
-
54
-		// TODO(sday): Ick! A goroutine per digest verification? We'll have to
55
-		// get the tarsum library to export an io.Writer variant.
56
-		go func() {
57
-			if _, err := io.Copy(ioutil.Discard, ts); err != nil {
58
-				pr.CloseWithError(err)
59
-			} else {
60
-				pr.Close()
61
-			}
62
-		}()
63
-
64
-		return &tarsumVerifier{
65
-			digest: d,
66
-			ts:     ts,
67
-			pr:     pr,
68
-			pw:     pw,
69
-		}, nil
70
-	}
71
-}
72
-
73
-// NewLengthVerifier returns a verifier that returns true when the number of
74
-// read bytes equals the expected parameter.
75
-func NewLengthVerifier(expected int64) Verifier {
76
-	return &lengthVerifier{
77
-		expected: expected,
78
-	}
79
-}
80
-
81
-type lengthVerifier struct {
82
-	expected int64 // expected bytes read
83
-	len      int64 // bytes read
84
-}
85
-
86
-func (lv *lengthVerifier) Write(p []byte) (n int, err error) {
87
-	n = len(p)
88
-	lv.len += int64(n)
89
-	return n, err
90
-}
91
-
92
-func (lv *lengthVerifier) Verified() bool {
93
-	return lv.expected == lv.len
30
+	return hashVerifier{
31
+		hash:   d.Algorithm().Hash(),
32
+		digest: d,
33
+	}, nil
94 34
 }
95 35
 
96 36
 type hashVerifier struct {
... ...
@@ -105,18 +42,3 @@ func (hv hashVerifier) Write(p []byte) (n int, err error) {
105 105
 func (hv hashVerifier) Verified() bool {
106 106
 	return hv.digest == NewDigest(hv.digest.Algorithm(), hv.hash)
107 107
 }
108
-
109
-type tarsumVerifier struct {
110
-	digest Digest
111
-	ts     tarsum.TarSum
112
-	pr     *io.PipeReader
113
-	pw     *io.PipeWriter
114
-}
115
-
116
-func (tv *tarsumVerifier) Write(p []byte) (n int, err error) {
117
-	return tv.pw.Write(p)
118
-}
119
-
120
-func (tv *tarsumVerifier) Verified() bool {
121
-	return tv.digest == Digest(tv.ts.Sum(nil))
122
-}
... ...
@@ -16,6 +16,15 @@ var ErrManifestNotModified = errors.New("manifest not modified")
16 16
 // performed
17 17
 var ErrUnsupported = errors.New("operation unsupported")
18 18
 
19
+// ErrTagUnknown is returned if the given tag is not known by the tag service
20
+type ErrTagUnknown struct {
21
+	Tag string
22
+}
23
+
24
+func (err ErrTagUnknown) Error() string {
25
+	return fmt.Sprintf("unknown tag=%s", err.Tag)
26
+}
27
+
19 28
 // ErrRepositoryUnknown is returned if the named repository is not known by
20 29
 // the registry.
21 30
 type ErrRepositoryUnknown struct {
22 31
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+package manifestlist
1
+
2
+import (
3
+	"encoding/json"
4
+	"errors"
5
+	"fmt"
6
+
7
+	"github.com/docker/distribution"
8
+	"github.com/docker/distribution/digest"
9
+	"github.com/docker/distribution/manifest"
10
+)
11
+
12
+// MediaTypeManifestList specifies the mediaType for manifest lists.
13
+const MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
14
+
15
+// SchemaVersion provides a pre-initialized version structure for this
16
+// packages version of the manifest.
17
+var SchemaVersion = manifest.Versioned{
18
+	SchemaVersion: 2,
19
+	MediaType:     MediaTypeManifestList,
20
+}
21
+
22
+func init() {
23
+	manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
24
+		m := new(DeserializedManifestList)
25
+		err := m.UnmarshalJSON(b)
26
+		if err != nil {
27
+			return nil, distribution.Descriptor{}, err
28
+		}
29
+
30
+		dgst := digest.FromBytes(b)
31
+		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
32
+	}
33
+	err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
34
+	if err != nil {
35
+		panic(fmt.Sprintf("Unable to register manifest: %s", err))
36
+	}
37
+}
38
+
39
+// PlatformSpec specifies a platform where a particular image manifest is
40
+// applicable.
41
+type PlatformSpec struct {
42
+	// Architecture field specifies the CPU architecture, for example
43
+	// `amd64` or `ppc64`.
44
+	Architecture string `json:"architecture"`
45
+
46
+	// OS specifies the operating system, for example `linux` or `windows`.
47
+	OS string `json:"os"`
48
+
49
+	// Variant is an optional field specifying a variant of the CPU, for
50
+	// example `ppc64le` to specify a little-endian version of a PowerPC CPU.
51
+	Variant string `json:"variant,omitempty"`
52
+
53
+	// Features is an optional field specifuing an array of strings, each
54
+	// listing a required CPU feature (for example `sse4` or `aes`).
55
+	Features []string `json:"features,omitempty"`
56
+}
57
+
58
+// A ManifestDescriptor references a platform-specific manifest.
59
+type ManifestDescriptor struct {
60
+	distribution.Descriptor
61
+
62
+	// Platform specifies which platform the manifest pointed to by the
63
+	// descriptor runs on.
64
+	Platform PlatformSpec `json:"platform"`
65
+}
66
+
67
+// ManifestList references manifests for various platforms.
68
+type ManifestList struct {
69
+	manifest.Versioned
70
+
71
+	// Config references the image configuration as a blob.
72
+	Manifests []ManifestDescriptor `json:"manifests"`
73
+}
74
+
75
+// References returnes the distribution descriptors for the referenced image
76
+// manifests.
77
+func (m ManifestList) References() []distribution.Descriptor {
78
+	dependencies := make([]distribution.Descriptor, len(m.Manifests))
79
+	for i := range m.Manifests {
80
+		dependencies[i] = m.Manifests[i].Descriptor
81
+	}
82
+
83
+	return dependencies
84
+}
85
+
86
+// DeserializedManifestList wraps ManifestList with a copy of the original
87
+// JSON.
88
+type DeserializedManifestList struct {
89
+	ManifestList
90
+
91
+	// canonical is the canonical byte representation of the Manifest.
92
+	canonical []byte
93
+}
94
+
95
+// FromDescriptors takes a slice of descriptors, and returns a
96
+// DeserializedManifestList which contains the resulting manifest list
97
+// and its JSON representation.
98
+func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
99
+	m := ManifestList{
100
+		Versioned: SchemaVersion,
101
+	}
102
+
103
+	m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors))
104
+	copy(m.Manifests, descriptors)
105
+
106
+	deserialized := DeserializedManifestList{
107
+		ManifestList: m,
108
+	}
109
+
110
+	var err error
111
+	deserialized.canonical, err = json.MarshalIndent(&m, "", "   ")
112
+	return &deserialized, err
113
+}
114
+
115
+// UnmarshalJSON populates a new ManifestList struct from JSON data.
116
+func (m *DeserializedManifestList) UnmarshalJSON(b []byte) error {
117
+	m.canonical = make([]byte, len(b), len(b))
118
+	// store manifest list in canonical
119
+	copy(m.canonical, b)
120
+
121
+	// Unmarshal canonical JSON into ManifestList object
122
+	var manifestList ManifestList
123
+	if err := json.Unmarshal(m.canonical, &manifestList); err != nil {
124
+		return err
125
+	}
126
+
127
+	m.ManifestList = manifestList
128
+
129
+	return nil
130
+}
131
+
132
+// MarshalJSON returns the contents of canonical. If canonical is empty,
133
+// marshals the inner contents.
134
+func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
135
+	if len(m.canonical) > 0 {
136
+		return m.canonical, nil
137
+	}
138
+
139
+	return nil, errors.New("JSON representation not initialized in DeserializedManifestList")
140
+}
141
+
142
+// Payload returns the raw content of the manifest list. The contents can be
143
+// used to calculate the content identifier.
144
+func (m DeserializedManifestList) Payload() (string, []byte, error) {
145
+	return m.MediaType, m.canonical, nil
146
+}
0 147
new file mode 100644
... ...
@@ -0,0 +1,278 @@
0
+package schema1
1
+
2
+import (
3
+	"crypto/sha512"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"time"
8
+
9
+	"github.com/docker/distribution"
10
+	"github.com/docker/distribution/context"
11
+	"github.com/docker/libtrust"
12
+
13
+	"github.com/docker/distribution/digest"
14
+	"github.com/docker/distribution/manifest"
15
+)
16
+
17
+type diffID digest.Digest
18
+
19
+// gzippedEmptyTar is a gzip-compressed version of an empty tar file
20
+// (1024 NULL bytes)
21
+var gzippedEmptyTar = []byte{
22
+	31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
23
+	0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
24
+}
25
+
26
+// digestSHA256GzippedEmptyTar is the canonical sha256 digest of
27
+// gzippedEmptyTar
28
+const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
29
+
30
+// configManifestBuilder is a type for constructing manifests from an image
31
+// configuration and generic descriptors.
32
+type configManifestBuilder struct {
33
+	// bs is a BlobService used to create empty layer tars in the
34
+	// blob store if necessary.
35
+	bs distribution.BlobService
36
+	// pk is the libtrust private key used to sign the final manifest.
37
+	pk libtrust.PrivateKey
38
+	// configJSON is configuration supplied when the ManifestBuilder was
39
+	// created.
40
+	configJSON []byte
41
+	// name is the name provided to NewConfigManifestBuilder
42
+	name string
43
+	// tag is the tag provided to NewConfigManifestBuilder
44
+	tag string
45
+	// descriptors is the set of descriptors referencing the layers.
46
+	descriptors []distribution.Descriptor
47
+	// emptyTarDigest is set to a valid digest if an empty tar has been
48
+	// put in the blob store; otherwise it is empty.
49
+	emptyTarDigest digest.Digest
50
+}
51
+
52
+// NewConfigManifestBuilder is used to build new manifests for the current
53
+// schema version from an image configuration and a set of descriptors.
54
+// It takes a BlobService so that it can add an empty tar to the blob store
55
+// if the resulting manifest needs empty layers.
56
+func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, name, tag string, configJSON []byte) distribution.ManifestBuilder {
57
+	return &configManifestBuilder{
58
+		bs:         bs,
59
+		pk:         pk,
60
+		configJSON: configJSON,
61
+		name:       name,
62
+		tag:        tag,
63
+	}
64
+}
65
+
66
+// Build produces a final manifest from the given references
67
+func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) {
68
+	type imageRootFS struct {
69
+		Type      string   `json:"type"`
70
+		DiffIDs   []diffID `json:"diff_ids,omitempty"`
71
+		BaseLayer string   `json:"base_layer,omitempty"`
72
+	}
73
+
74
+	type imageHistory struct {
75
+		Created    time.Time `json:"created"`
76
+		Author     string    `json:"author,omitempty"`
77
+		CreatedBy  string    `json:"created_by,omitempty"`
78
+		Comment    string    `json:"comment,omitempty"`
79
+		EmptyLayer bool      `json:"empty_layer,omitempty"`
80
+	}
81
+
82
+	type imageConfig struct {
83
+		RootFS       *imageRootFS   `json:"rootfs,omitempty"`
84
+		History      []imageHistory `json:"history,omitempty"`
85
+		Architecture string         `json:"architecture,omitempty"`
86
+	}
87
+
88
+	var img imageConfig
89
+
90
+	if err := json.Unmarshal(mb.configJSON, &img); err != nil {
91
+		return nil, err
92
+	}
93
+
94
+	if len(img.History) == 0 {
95
+		return nil, errors.New("empty history when trying to create schema1 manifest")
96
+	}
97
+
98
+	if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
99
+		return nil, errors.New("number of descriptors and number of layers in rootfs must match")
100
+	}
101
+
102
+	// Generate IDs for each layer
103
+	// For non-top-level layers, create fake V1Compatibility strings that
104
+	// fit the format and don't collide with anything else, but don't
105
+	// result in runnable images on their own.
106
+	type v1Compatibility struct {
107
+		ID              string    `json:"id"`
108
+		Parent          string    `json:"parent,omitempty"`
109
+		Comment         string    `json:"comment,omitempty"`
110
+		Created         time.Time `json:"created"`
111
+		ContainerConfig struct {
112
+			Cmd []string
113
+		} `json:"container_config,omitempty"`
114
+		ThrowAway bool `json:"throwaway,omitempty"`
115
+	}
116
+
117
+	fsLayerList := make([]FSLayer, len(img.History))
118
+	history := make([]History, len(img.History))
119
+
120
+	parent := ""
121
+	layerCounter := 0
122
+	for i, h := range img.History[:len(img.History)-1] {
123
+		var blobsum digest.Digest
124
+		if h.EmptyLayer {
125
+			if blobsum, err = mb.emptyTar(ctx); err != nil {
126
+				return nil, err
127
+			}
128
+		} else {
129
+			if len(img.RootFS.DiffIDs) <= layerCounter {
130
+				return nil, errors.New("too many non-empty layers in History section")
131
+			}
132
+			blobsum = mb.descriptors[layerCounter].Digest
133
+			layerCounter++
134
+		}
135
+
136
+		v1ID := digest.FromBytes([]byte(blobsum.Hex() + " " + parent)).Hex()
137
+
138
+		if i == 0 && img.RootFS.BaseLayer != "" {
139
+			// windows-only baselayer setup
140
+			baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer))
141
+			parent = fmt.Sprintf("%x", baseID[:32])
142
+		}
143
+
144
+		v1Compatibility := v1Compatibility{
145
+			ID:      v1ID,
146
+			Parent:  parent,
147
+			Comment: h.Comment,
148
+			Created: h.Created,
149
+		}
150
+		v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
151
+		if h.EmptyLayer {
152
+			v1Compatibility.ThrowAway = true
153
+		}
154
+		jsonBytes, err := json.Marshal(&v1Compatibility)
155
+		if err != nil {
156
+			return nil, err
157
+		}
158
+
159
+		reversedIndex := len(img.History) - i - 1
160
+		history[reversedIndex].V1Compatibility = string(jsonBytes)
161
+		fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum}
162
+
163
+		parent = v1ID
164
+	}
165
+
166
+	latestHistory := img.History[len(img.History)-1]
167
+
168
+	var blobsum digest.Digest
169
+	if latestHistory.EmptyLayer {
170
+		if blobsum, err = mb.emptyTar(ctx); err != nil {
171
+			return nil, err
172
+		}
173
+	} else {
174
+		if len(img.RootFS.DiffIDs) <= layerCounter {
175
+			return nil, errors.New("too many non-empty layers in History section")
176
+		}
177
+		blobsum = mb.descriptors[layerCounter].Digest
178
+	}
179
+
180
+	fsLayerList[0] = FSLayer{BlobSum: blobsum}
181
+	dgst := digest.FromBytes([]byte(blobsum.Hex() + " " + parent + " " + string(mb.configJSON)))
182
+
183
+	// Top-level v1compatibility string should be a modified version of the
184
+	// image config.
185
+	transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Hex(), parent, latestHistory.EmptyLayer)
186
+	if err != nil {
187
+		return nil, err
188
+	}
189
+
190
+	history[0].V1Compatibility = string(transformedConfig)
191
+
192
+	mfst := Manifest{
193
+		Versioned: manifest.Versioned{
194
+			SchemaVersion: 1,
195
+		},
196
+		Name:         mb.name,
197
+		Tag:          mb.tag,
198
+		Architecture: img.Architecture,
199
+		FSLayers:     fsLayerList,
200
+		History:      history,
201
+	}
202
+
203
+	return Sign(&mfst, mb.pk)
204
+}
205
+
206
+// emptyTar pushes a compressed empty tar to the blob store if one doesn't
207
+// already exist, and returns its blobsum.
208
+func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
209
+	if mb.emptyTarDigest != "" {
210
+		// Already put an empty tar
211
+		return mb.emptyTarDigest, nil
212
+	}
213
+
214
+	descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar)
215
+	switch err {
216
+	case nil:
217
+		mb.emptyTarDigest = descriptor.Digest
218
+		return descriptor.Digest, nil
219
+	case distribution.ErrBlobUnknown:
220
+		// nop
221
+	default:
222
+		return "", err
223
+	}
224
+
225
+	// Add gzipped empty tar to the blob store
226
+	descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar)
227
+	if err != nil {
228
+		return "", err
229
+	}
230
+
231
+	mb.emptyTarDigest = descriptor.Digest
232
+
233
+	return descriptor.Digest, nil
234
+}
235
+
236
+// AppendReference adds a reference to the current ManifestBuilder
237
+func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error {
238
+	// todo: verification here?
239
+	mb.descriptors = append(mb.descriptors, d.Descriptor())
240
+	return nil
241
+}
242
+
243
+// References returns the current references added to this builder
244
+func (mb *configManifestBuilder) References() []distribution.Descriptor {
245
+	return mb.descriptors
246
+}
247
+
248
+// MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON
249
+func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
250
+	// Top-level v1compatibility string should be a modified version of the
251
+	// image config.
252
+	var configAsMap map[string]*json.RawMessage
253
+	if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
254
+		return nil, err
255
+	}
256
+
257
+	// Delete fields that didn't exist in old manifest
258
+	delete(configAsMap, "rootfs")
259
+	delete(configAsMap, "history")
260
+	configAsMap["id"] = rawJSON(v1ID)
261
+	if parentV1ID != "" {
262
+		configAsMap["parent"] = rawJSON(parentV1ID)
263
+	}
264
+	if throwaway {
265
+		configAsMap["throwaway"] = rawJSON(true)
266
+	}
267
+
268
+	return json.Marshal(configAsMap)
269
+}
270
+
271
+func rawJSON(value interface{}) *json.RawMessage {
272
+	jsonval, err := json.Marshal(value)
273
+	if err != nil {
274
+		return nil
275
+	}
276
+	return (*json.RawMessage)(&jsonval)
277
+}
... ...
@@ -2,20 +2,22 @@ package schema1
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 
7
+	"github.com/docker/distribution"
6 8
 	"github.com/docker/distribution/digest"
7 9
 	"github.com/docker/distribution/manifest"
8 10
 	"github.com/docker/libtrust"
9 11
 )
10 12
 
11
-// TODO(stevvooe): When we rev the manifest format, the contents of this
12
-// package should be moved to manifest/v1.
13
-
14 13
 const (
15
-	// ManifestMediaType specifies the mediaType for the current version. Note
16
-	// that for schema version 1, the the media is optionally
17
-	// "application/json".
18
-	ManifestMediaType = "application/vnd.docker.distribution.manifest.v1+json"
14
+	// MediaTypeManifest specifies the mediaType for the current version. Note
15
+	// that for schema version 1, the the media is optionally "application/json".
16
+	MediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json"
17
+	// MediaTypeSignedManifest specifies the mediatype for current SignedManifest version
18
+	MediaTypeSignedManifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
19
+	// MediaTypeManifestLayer specifies the media type for manifest layers
20
+	MediaTypeManifestLayer = "application/vnd.docker.container.image.rootfs.diff+x-gtar"
19 21
 )
20 22
 
21 23
 var (
... ...
@@ -26,6 +28,47 @@ var (
26 26
 	}
27 27
 )
28 28
 
29
+func init() {
30
+	schema1Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
31
+		sm := new(SignedManifest)
32
+		err := sm.UnmarshalJSON(b)
33
+		if err != nil {
34
+			return nil, distribution.Descriptor{}, err
35
+		}
36
+
37
+		desc := distribution.Descriptor{
38
+			Digest:    digest.FromBytes(sm.Canonical),
39
+			Size:      int64(len(sm.Canonical)),
40
+			MediaType: MediaTypeManifest,
41
+		}
42
+		return sm, desc, err
43
+	}
44
+	err := distribution.RegisterManifestSchema(MediaTypeManifest, schema1Func)
45
+	if err != nil {
46
+		panic(fmt.Sprintf("Unable to register manifest: %s", err))
47
+	}
48
+	err = distribution.RegisterManifestSchema("", schema1Func)
49
+	if err != nil {
50
+		panic(fmt.Sprintf("Unable to register manifest: %s", err))
51
+	}
52
+	err = distribution.RegisterManifestSchema("application/json; charset=utf-8", schema1Func)
53
+	if err != nil {
54
+		panic(fmt.Sprintf("Unable to register manifest: %s", err))
55
+	}
56
+}
57
+
58
+// FSLayer is a container struct for BlobSums defined in an image manifest
59
+type FSLayer struct {
60
+	// BlobSum is the tarsum of the referenced filesystem image layer
61
+	BlobSum digest.Digest `json:"blobSum"`
62
+}
63
+
64
+// History stores unstructured v1 compatibility information
65
+type History struct {
66
+	// V1Compatibility is the raw v1 compatibility information
67
+	V1Compatibility string `json:"v1Compatibility"`
68
+}
69
+
29 70
 // Manifest provides the base accessible fields for working with V2 image
30 71
 // format in the registry.
31 72
 type Manifest struct {
... ...
@@ -49,59 +92,64 @@ type Manifest struct {
49 49
 }
50 50
 
51 51
 // SignedManifest provides an envelope for a signed image manifest, including
52
-// the format sensitive raw bytes. It contains fields to
52
+// the format sensitive raw bytes.
53 53
 type SignedManifest struct {
54 54
 	Manifest
55 55
 
56
-	// Raw is the byte representation of the ImageManifest, used for signature
57
-	// verification. The value of Raw must be used directly during
58
-	// serialization, or the signature check will fail. The manifest byte
56
+	// Canonical is the canonical byte representation of the ImageManifest,
57
+	// without any attached signatures. The manifest byte
59 58
 	// representation cannot change or it will have to be re-signed.
60
-	Raw []byte `json:"-"`
59
+	Canonical []byte `json:"-"`
60
+
61
+	// all contains the byte representation of the Manifest including signatures
62
+	// and is retuend by Payload()
63
+	all []byte
61 64
 }
62 65
 
63
-// UnmarshalJSON populates a new ImageManifest struct from JSON data.
66
+// UnmarshalJSON populates a new SignedManifest struct from JSON data.
64 67
 func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
65
-	sm.Raw = make([]byte, len(b), len(b))
66
-	copy(sm.Raw, b)
68
+	sm.all = make([]byte, len(b), len(b))
69
+	// store manifest and signatures in all
70
+	copy(sm.all, b)
67 71
 
68
-	p, err := sm.Payload()
72
+	jsig, err := libtrust.ParsePrettySignature(b, "signatures")
69 73
 	if err != nil {
70 74
 		return err
71 75
 	}
72 76
 
77
+	// Resolve the payload in the manifest.
78
+	bytes, err := jsig.Payload()
79
+	if err != nil {
80
+		return err
81
+	}
82
+
83
+	// sm.Canonical stores the canonical manifest JSON
84
+	sm.Canonical = make([]byte, len(bytes), len(bytes))
85
+	copy(sm.Canonical, bytes)
86
+
87
+	// Unmarshal canonical JSON into Manifest object
73 88
 	var manifest Manifest
74
-	if err := json.Unmarshal(p, &manifest); err != nil {
89
+	if err := json.Unmarshal(sm.Canonical, &manifest); err != nil {
75 90
 		return err
76 91
 	}
77 92
 
78 93
 	sm.Manifest = manifest
94
+
79 95
 	return nil
80 96
 }
81 97
 
82
-// Payload returns the raw, signed content of the signed manifest. The
83
-// contents can be used to calculate the content identifier.
84
-func (sm *SignedManifest) Payload() ([]byte, error) {
85
-	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
86
-	if err != nil {
87
-		return nil, err
98
+// References returnes the descriptors of this manifests references
99
+func (sm SignedManifest) References() []distribution.Descriptor {
100
+	dependencies := make([]distribution.Descriptor, len(sm.FSLayers))
101
+	for i, fsLayer := range sm.FSLayers {
102
+		dependencies[i] = distribution.Descriptor{
103
+			MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar",
104
+			Digest:    fsLayer.BlobSum,
105
+		}
88 106
 	}
89 107
 
90
-	// Resolve the payload in the manifest.
91
-	return jsig.Payload()
92
-}
93
-
94
-// Signatures returns the signatures as provided by
95
-// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
96
-// signatures.
97
-func (sm *SignedManifest) Signatures() ([][]byte, error) {
98
-	jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
99
-	if err != nil {
100
-		return nil, err
101
-	}
108
+	return dependencies
102 109
 
103
-	// Resolve the payload in the manifest.
104
-	return jsig.Signatures()
105 110
 }
106 111
 
107 112
 // MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
... ...
@@ -109,22 +157,28 @@ func (sm *SignedManifest) Signatures() ([][]byte, error) {
109 109
 // use Raw directly, since the the content produced by json.Marshal will be
110 110
 // compacted and will fail signature checks.
111 111
 func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
112
-	if len(sm.Raw) > 0 {
113
-		return sm.Raw, nil
112
+	if len(sm.all) > 0 {
113
+		return sm.all, nil
114 114
 	}
115 115
 
116 116
 	// If the raw data is not available, just dump the inner content.
117 117
 	return json.Marshal(&sm.Manifest)
118 118
 }
119 119
 
120
-// FSLayer is a container struct for BlobSums defined in an image manifest
121
-type FSLayer struct {
122
-	// BlobSum is the tarsum of the referenced filesystem image layer
123
-	BlobSum digest.Digest `json:"blobSum"`
120
+// Payload returns the signed content of the signed manifest.
121
+func (sm SignedManifest) Payload() (string, []byte, error) {
122
+	return MediaTypeManifest, sm.all, nil
124 123
 }
125 124
 
126
-// History stores unstructured v1 compatibility information
127
-type History struct {
128
-	// V1Compatibility is the raw v1 compatibility information
129
-	V1Compatibility string `json:"v1Compatibility"`
125
+// Signatures returns the signatures as provided by
126
+// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
127
+// signatures.
128
+func (sm *SignedManifest) Signatures() ([][]byte, error) {
129
+	jsig, err := libtrust.ParsePrettySignature(sm.all, "signatures")
130
+	if err != nil {
131
+		return nil, err
132
+	}
133
+
134
+	// Resolve the payload in the manifest.
135
+	return jsig.Signatures()
130 136
 }
131 137
new file mode 100644
... ...
@@ -0,0 +1,92 @@
0
+package schema1
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"errors"
6
+	"github.com/docker/distribution"
7
+	"github.com/docker/distribution/context"
8
+	"github.com/docker/distribution/digest"
9
+	"github.com/docker/distribution/manifest"
10
+	"github.com/docker/libtrust"
11
+)
12
+
13
+// referenceManifestBuilder is a type for constructing manifests from schema1
14
+// dependencies.
15
+type referenceManifestBuilder struct {
16
+	Manifest
17
+	pk libtrust.PrivateKey
18
+}
19
+
20
+// NewReferenceManifestBuilder is used to build new manifests for the current
21
+// schema version using schema1 dependencies.
22
+func NewReferenceManifestBuilder(pk libtrust.PrivateKey, name, tag, architecture string) distribution.ManifestBuilder {
23
+	return &referenceManifestBuilder{
24
+		Manifest: Manifest{
25
+			Versioned: manifest.Versioned{
26
+				SchemaVersion: 1,
27
+			},
28
+			Name:         name,
29
+			Tag:          tag,
30
+			Architecture: architecture,
31
+		},
32
+		pk: pk,
33
+	}
34
+}
35
+
36
+func (mb *referenceManifestBuilder) Build(ctx context.Context) (distribution.Manifest, error) {
37
+	m := mb.Manifest
38
+	if len(m.FSLayers) == 0 {
39
+		return nil, errors.New("cannot build manifest with zero layers or history")
40
+	}
41
+
42
+	m.FSLayers = make([]FSLayer, len(mb.Manifest.FSLayers))
43
+	m.History = make([]History, len(mb.Manifest.History))
44
+	copy(m.FSLayers, mb.Manifest.FSLayers)
45
+	copy(m.History, mb.Manifest.History)
46
+
47
+	return Sign(&m, mb.pk)
48
+}
49
+
50
+// AppendReference adds a reference to the current ManifestBuilder
51
+func (mb *referenceManifestBuilder) AppendReference(d distribution.Describable) error {
52
+	r, ok := d.(Reference)
53
+	if !ok {
54
+		return fmt.Errorf("Unable to add non-reference type to v1 builder")
55
+	}
56
+
57
+	// Entries need to be prepended
58
+	mb.Manifest.FSLayers = append([]FSLayer{{BlobSum: r.Digest}}, mb.Manifest.FSLayers...)
59
+	mb.Manifest.History = append([]History{r.History}, mb.Manifest.History...)
60
+	return nil
61
+
62
+}
63
+
64
+// References returns the current references added to this builder
65
+func (mb *referenceManifestBuilder) References() []distribution.Descriptor {
66
+	refs := make([]distribution.Descriptor, len(mb.Manifest.FSLayers))
67
+	for i := range mb.Manifest.FSLayers {
68
+		layerDigest := mb.Manifest.FSLayers[i].BlobSum
69
+		history := mb.Manifest.History[i]
70
+		ref := Reference{layerDigest, 0, history}
71
+		refs[i] = ref.Descriptor()
72
+	}
73
+	return refs
74
+}
75
+
76
+// Reference describes a manifest v2, schema version 1 dependency.
77
+// An FSLayer associated with a history entry.
78
+type Reference struct {
79
+	Digest  digest.Digest
80
+	Size    int64 // if we know it, set it for the descriptor.
81
+	History History
82
+}
83
+
84
+// Descriptor describes a reference
85
+func (r Reference) Descriptor() distribution.Descriptor {
86
+	return distribution.Descriptor{
87
+		MediaType: MediaTypeManifestLayer,
88
+		Digest:    r.Digest,
89
+		Size:      r.Size,
90
+	}
91
+}
... ...
@@ -31,8 +31,9 @@ func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) {
31 31
 	}
32 32
 
33 33
 	return &SignedManifest{
34
-		Manifest: *m,
35
-		Raw:      pretty,
34
+		Manifest:  *m,
35
+		all:       pretty,
36
+		Canonical: p,
36 37
 	}, nil
37 38
 }
38 39
 
... ...
@@ -60,7 +61,8 @@ func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certifica
60 60
 	}
61 61
 
62 62
 	return &SignedManifest{
63
-		Manifest: *m,
64
-		Raw:      pretty,
63
+		Manifest:  *m,
64
+		all:       pretty,
65
+		Canonical: p,
65 66
 	}, nil
66 67
 }
... ...
@@ -10,7 +10,7 @@ import (
10 10
 // Verify verifies the signature of the signed manifest returning the public
11 11
 // keys used during signing.
12 12
 func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) {
13
-	js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
13
+	js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
14 14
 	if err != nil {
15 15
 		logrus.WithField("err", err).Debugf("(*SignedManifest).Verify")
16 16
 		return nil, err
... ...
@@ -23,7 +23,7 @@ func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) {
23 23
 // certificate pool returning the list of verified chains. Signatures without
24 24
 // an x509 chain are not checked.
25 25
 func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) {
26
-	js, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
26
+	js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
27 27
 	if err != nil {
28 28
 		return nil, err
29 29
 	}
30 30
new file mode 100644
... ...
@@ -0,0 +1,74 @@
0
+package schema2
1
+
2
+import (
3
+	"github.com/docker/distribution"
4
+	"github.com/docker/distribution/context"
5
+	"github.com/docker/distribution/digest"
6
+)
7
+
8
+// builder is a type for constructing manifests.
9
+type builder struct {
10
+	// bs is a BlobService used to publish the configuration blob.
11
+	bs distribution.BlobService
12
+
13
+	// configJSON references
14
+	configJSON []byte
15
+
16
+	// layers is a list of layer descriptors that gets built by successive
17
+	// calls to AppendReference.
18
+	layers []distribution.Descriptor
19
+}
20
+
21
+// NewManifestBuilder is used to build new manifests for the current schema
22
+// version. It takes a BlobService so it can publish the configuration blob
23
+// as part of the Build process.
24
+func NewManifestBuilder(bs distribution.BlobService, configJSON []byte) distribution.ManifestBuilder {
25
+	mb := &builder{
26
+		bs:         bs,
27
+		configJSON: make([]byte, len(configJSON)),
28
+	}
29
+	copy(mb.configJSON, configJSON)
30
+
31
+	return mb
32
+}
33
+
34
+// Build produces a final manifest from the given references.
35
+func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) {
36
+	m := Manifest{
37
+		Versioned: SchemaVersion,
38
+		Layers:    make([]distribution.Descriptor, len(mb.layers)),
39
+	}
40
+	copy(m.Layers, mb.layers)
41
+
42
+	configDigest := digest.FromBytes(mb.configJSON)
43
+
44
+	var err error
45
+	m.Config, err = mb.bs.Stat(ctx, configDigest)
46
+	switch err {
47
+	case nil:
48
+		return FromStruct(m)
49
+	case distribution.ErrBlobUnknown:
50
+		// nop
51
+	default:
52
+		return nil, err
53
+	}
54
+
55
+	// Add config to the blob store
56
+	m.Config, err = mb.bs.Put(ctx, MediaTypeConfig, mb.configJSON)
57
+	if err != nil {
58
+		return nil, err
59
+	}
60
+
61
+	return FromStruct(m)
62
+}
63
+
64
+// AppendReference adds a reference to the current ManifestBuilder.
65
+func (mb *builder) AppendReference(d distribution.Describable) error {
66
+	mb.layers = append(mb.layers, d.Descriptor())
67
+	return nil
68
+}
69
+
70
+// References returns the current references added to this builder.
71
+func (mb *builder) References() []distribution.Descriptor {
72
+	return mb.layers
73
+}
0 74
new file mode 100644
... ...
@@ -0,0 +1,125 @@
0
+package schema2
1
+
2
+import (
3
+	"encoding/json"
4
+	"errors"
5
+	"fmt"
6
+
7
+	"github.com/docker/distribution"
8
+	"github.com/docker/distribution/digest"
9
+	"github.com/docker/distribution/manifest"
10
+)
11
+
12
+const (
13
+	// MediaTypeManifest specifies the mediaType for the current version.
14
+	MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json"
15
+
16
+	// MediaTypeConfig specifies the mediaType for the image configuration.
17
+	MediaTypeConfig = "application/vnd.docker.container.image.v1+json"
18
+
19
+	// MediaTypeLayer is the mediaType used for layers referenced by the
20
+	// manifest.
21
+	MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip"
22
+)
23
+
24
+var (
25
+	// SchemaVersion provides a pre-initialized version structure for this
26
+	// packages version of the manifest.
27
+	SchemaVersion = manifest.Versioned{
28
+		SchemaVersion: 2,
29
+		MediaType:     MediaTypeManifest,
30
+	}
31
+)
32
+
33
+func init() {
34
+	schema2Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
35
+		m := new(DeserializedManifest)
36
+		err := m.UnmarshalJSON(b)
37
+		if err != nil {
38
+			return nil, distribution.Descriptor{}, err
39
+		}
40
+
41
+		dgst := digest.FromBytes(b)
42
+		return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifest}, err
43
+	}
44
+	err := distribution.RegisterManifestSchema(MediaTypeManifest, schema2Func)
45
+	if err != nil {
46
+		panic(fmt.Sprintf("Unable to register manifest: %s", err))
47
+	}
48
+}
49
+
50
+// Manifest defines a schema2 manifest.
51
+type Manifest struct {
52
+	manifest.Versioned
53
+
54
+	// Config references the image configuration as a blob.
55
+	Config distribution.Descriptor `json:"config"`
56
+
57
+	// Layers lists descriptors for the layers referenced by the
58
+	// configuration.
59
+	Layers []distribution.Descriptor `json:"layers"`
60
+}
61
+
62
+// References returnes the descriptors of this manifests references.
63
+func (m Manifest) References() []distribution.Descriptor {
64
+	return m.Layers
65
+
66
+}
67
+
68
+// Target returns the target of this signed manifest.
69
+func (m Manifest) Target() distribution.Descriptor {
70
+	return m.Config
71
+}
72
+
73
+// DeserializedManifest wraps Manifest with a copy of the original JSON.
74
+// It satisfies the distribution.Manifest interface.
75
+type DeserializedManifest struct {
76
+	Manifest
77
+
78
+	// canonical is the canonical byte representation of the Manifest.
79
+	canonical []byte
80
+}
81
+
82
+// FromStruct takes a Manifest structure, marshals it to JSON, and returns a
83
+// DeserializedManifest which contains the manifest and its JSON representation.
84
+func FromStruct(m Manifest) (*DeserializedManifest, error) {
85
+	var deserialized DeserializedManifest
86
+	deserialized.Manifest = m
87
+
88
+	var err error
89
+	deserialized.canonical, err = json.MarshalIndent(&m, "", "   ")
90
+	return &deserialized, err
91
+}
92
+
93
+// UnmarshalJSON populates a new Manifest struct from JSON data.
94
+func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
95
+	m.canonical = make([]byte, len(b), len(b))
96
+	// store manifest in canonical
97
+	copy(m.canonical, b)
98
+
99
+	// Unmarshal canonical JSON into Manifest object
100
+	var manifest Manifest
101
+	if err := json.Unmarshal(m.canonical, &manifest); err != nil {
102
+		return err
103
+	}
104
+
105
+	m.Manifest = manifest
106
+
107
+	return nil
108
+}
109
+
110
+// MarshalJSON returns the contents of canonical. If canonical is empty,
111
+// marshals the inner contents.
112
+func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
113
+	if len(m.canonical) > 0 {
114
+		return m.canonical, nil
115
+	}
116
+
117
+	return nil, errors.New("JSON representation not initialized in DeserializedManifest")
118
+}
119
+
120
+// Payload returns the raw content of the manifest. The contents can be used to
121
+// calculate the content identifier.
122
+func (m DeserializedManifest) Payload() (string, []byte, error) {
123
+	return m.MediaType, m.canonical, nil
124
+}
... ...
@@ -1,9 +1,12 @@
1 1
 package manifest
2 2
 
3
-// Versioned provides a struct with just the manifest schemaVersion. Incoming
3
+// Versioned provides a struct with the manifest schemaVersion and . Incoming
4 4
 // content with unknown schema version can be decoded against this struct to
5 5
 // check the version.
6 6
 type Versioned struct {
7 7
 	// SchemaVersion is the image manifest schema that this image follows
8 8
 	SchemaVersion int `json:"schemaVersion"`
9
+
10
+	// MediaType is the media type of this schema.
11
+	MediaType string `json:"mediaType,omitempty"`
9 12
 }
10 13
new file mode 100644
... ...
@@ -0,0 +1,100 @@
0
+package distribution
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/distribution/context"
6
+	"github.com/docker/distribution/digest"
7
+)
8
+
9
+// Manifest represents a registry object specifying a set of
10
+// references and an optional target
11
+type Manifest interface {
12
+	// References returns a list of objects which make up this manifest.
13
+	// The references are strictly ordered from base to head. A reference
14
+	// is anything which can be represented by a distribution.Descriptor
15
+	References() []Descriptor
16
+
17
+	// Payload provides the serialized format of the manifest, in addition to
18
+	// the mediatype.
19
+	Payload() (mediatype string, payload []byte, err error)
20
+}
21
+
22
+// ManifestBuilder creates a manifest allowing one to include dependencies.
23
+// Instances can be obtained from a version-specific manifest package.  Manifest
24
+// specific data is passed into the function which creates the builder.
25
+type ManifestBuilder interface {
26
+	// Build creates the manifest from his builder.
27
+	Build(ctx context.Context) (Manifest, error)
28
+
29
+	// References returns a list of objects which have been added to this
30
+	// builder. The dependencies are returned in the order they were added,
31
+	// which should be from base to head.
32
+	References() []Descriptor
33
+
34
+	// AppendReference includes the given object in the manifest after any
35
+	// existing dependencies. If the add fails, such as when adding an
36
+	// unsupported dependency, an error may be returned.
37
+	AppendReference(dependency Describable) error
38
+}
39
+
40
+// ManifestService describes operations on image manifests.
41
+type ManifestService interface {
42
+	// Exists returns true if the manifest exists.
43
+	Exists(ctx context.Context, dgst digest.Digest) (bool, error)
44
+
45
+	// Get retrieves the manifest specified by the given digest
46
+	Get(ctx context.Context, dgst digest.Digest, options ...ManifestServiceOption) (Manifest, error)
47
+
48
+	// Put creates or updates the given manifest returning the manifest digest
49
+	Put(ctx context.Context, manifest Manifest, options ...ManifestServiceOption) (digest.Digest, error)
50
+
51
+	// Delete removes the manifest specified by the given digest. Deleting
52
+	// a manifest that doesn't exist will return ErrManifestNotFound
53
+	Delete(ctx context.Context, dgst digest.Digest) error
54
+
55
+	// Enumerate fills 'manifests' with the manifests in this service up
56
+	// to the size of 'manifests' and returns 'n' for the number of entries
57
+	// which were filled.  'last' contains an offset in the manifest set
58
+	// and can be used to resume iteration.
59
+	//Enumerate(ctx context.Context, manifests []Manifest, last Manifest) (n int, err error)
60
+}
61
+
62
+// Describable is an interface for descriptors
63
+type Describable interface {
64
+	Descriptor() Descriptor
65
+}
66
+
67
+// ManifestMediaTypes returns the supported media types for manifests.
68
+func ManifestMediaTypes() (mediaTypes []string) {
69
+	for t := range mappings {
70
+		mediaTypes = append(mediaTypes, t)
71
+	}
72
+	return
73
+}
74
+
75
+// UnmarshalFunc implements manifest unmarshalling a given MediaType
76
+type UnmarshalFunc func([]byte) (Manifest, Descriptor, error)
77
+
78
+var mappings = make(map[string]UnmarshalFunc, 0)
79
+
80
+// UnmarshalManifest looks up manifest unmarshall functions based on
81
+// MediaType
82
+func UnmarshalManifest(mediatype string, p []byte) (Manifest, Descriptor, error) {
83
+	unmarshalFunc, ok := mappings[mediatype]
84
+	if !ok {
85
+		return nil, Descriptor{}, fmt.Errorf("unsupported manifest mediatype: %s", mediatype)
86
+	}
87
+
88
+	return unmarshalFunc(p)
89
+}
90
+
91
+// RegisterManifestSchema registers an UnmarshalFunc for a given schema type.  This
92
+// should be called from specific
93
+func RegisterManifestSchema(mediatype string, u UnmarshalFunc) error {
94
+	if _, ok := mappings[mediatype]; ok {
95
+		return fmt.Errorf("manifest mediatype registration would overwrite existing: %s", mediatype)
96
+	}
97
+	mappings[mediatype] = u
98
+	return nil
99
+}
... ...
@@ -4,22 +4,16 @@
4 4
 // Grammar
5 5
 //
6 6
 // 	reference                       := repository [ ":" tag ] [ "@" digest ]
7
+//	name                            := [hostname '/'] component ['/' component]*
8
+//	hostname                        := hostcomponent ['.' hostcomponent]* [':' port-number]
9
+//	hostcomponent                   := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/
10
+//	port-number                     := /[0-9]+/
11
+//	component                       := alpha-numeric [separator alpha-numeric]*
12
+// 	alpha-numeric                   := /[a-z0-9]+/
13
+//	separator                       := /[_.]|__|[-]*/
7 14
 //
8
-//	// repository.go
9
-//	repository			:= hostname ['/' component]+
10
-//	hostname			:= hostcomponent [':' port-number]
11
-//	component			:= subcomponent [separator subcomponent]*
12
-//	subcomponent			:= alpha-numeric ['-'* alpha-numeric]*
13
-//	hostcomponent                   := [hostpart '.']* hostpart
14
-// 	alpha-numeric			:= /[a-z0-9]+/
15
-//	separator			:= /([_.]|__)/
16
-//	port-number			:= /[0-9]+/
17
-//	hostpart                        := /([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])/
18
-//
19
-//	// tag.go
20 15
 //	tag                             := /[\w][\w.-]{0,127}/
21 16
 //
22
-//	// from the digest package
23 17
 //	digest                          := digest-algorithm ":" digest-hex
24 18
 //	digest-algorithm                := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ]
25 19
 //	digest-algorithm-separator      := /[+.-_]/
... ...
@@ -52,8 +46,7 @@ var (
52 52
 	// ErrNameEmpty is returned for empty, invalid repository names.
53 53
 	ErrNameEmpty = errors.New("repository name must have at least one component")
54 54
 
55
-	// ErrNameTooLong is returned when a repository name is longer than
56
-	// RepositoryNameTotalLengthMax
55
+	// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
57 56
 	ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax)
58 57
 )
59 58
 
... ...
@@ -3,47 +3,122 @@ package reference
3 3
 import "regexp"
4 4
 
5 5
 var (
6
-	// nameSubComponentRegexp defines the part of the name which must be
7
-	// begin and end with an alphanumeric character. These characters can
8
-	// be separated by any number of dashes.
9
-	nameSubComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[-]+[a-z0-9]+)*`)
6
+	// alphaNumericRegexp defines the alpha numeric atom, typically a
7
+	// component of names. This only allows lower case characters and digits.
8
+	alphaNumericRegexp = match(`[a-z0-9]+`)
10 9
 
11
-	// nameComponentRegexp restricts registry path component names to
12
-	// start with at least one letter or number, with following parts able to
13
-	// be separated by one period, underscore or double underscore.
14
-	nameComponentRegexp = regexp.MustCompile(nameSubComponentRegexp.String() + `(?:(?:[._]|__)` + nameSubComponentRegexp.String() + `)*`)
10
+	// separatorRegexp defines the separators allowed to be embedded in name
11
+	// components. This allow one period, one or two underscore and multiple
12
+	// dashes.
13
+	separatorRegexp = match(`(?:[._]|__|[-]*)`)
15 14
 
16
-	nameRegexp = regexp.MustCompile(`(?:` + nameComponentRegexp.String() + `/)*` + nameComponentRegexp.String())
15
+	// nameComponentRegexp restricts registry path component names to start
16
+	// with at least one letter or number, with following parts able to be
17
+	// separated by one period, one or two underscore and multiple dashes.
18
+	nameComponentRegexp = expression(
19
+		alphaNumericRegexp,
20
+		optional(repeated(separatorRegexp, alphaNumericRegexp)))
17 21
 
18
-	hostnameComponentRegexp = regexp.MustCompile(`(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])`)
22
+	// hostnameComponentRegexp restricts the registry hostname component of a
23
+	// repository name to start with a component as defined by hostnameRegexp
24
+	// and followed by an optional port.
25
+	hostnameComponentRegexp = match(`(?:[a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])`)
19 26
 
20
-	// hostnameComponentRegexp restricts the registry hostname component of a repository name to
21
-	// start with a component as defined by hostnameRegexp and followed by an optional port.
22
-	hostnameRegexp = regexp.MustCompile(`(?:` + hostnameComponentRegexp.String() + `\.)*` + hostnameComponentRegexp.String() + `(?::[0-9]+)?`)
27
+	// hostnameRegexp defines the structure of potential hostname components
28
+	// that may be part of image names. This is purposely a subset of what is
29
+	// allowed by DNS to ensure backwards compatibility with Docker image
30
+	// names.
31
+	hostnameRegexp = expression(
32
+		hostnameComponentRegexp,
33
+		optional(repeated(literal(`.`), hostnameComponentRegexp)),
34
+		optional(literal(`:`), match(`[0-9]+`)))
23 35
 
24 36
 	// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
25
-	TagRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
37
+	TagRegexp = match(`[\w][\w.-]{0,127}`)
26 38
 
27 39
 	// anchoredTagRegexp matches valid tag names, anchored at the start and
28 40
 	// end of the matched string.
29
-	anchoredTagRegexp = regexp.MustCompile(`^` + TagRegexp.String() + `$`)
41
+	anchoredTagRegexp = anchored(TagRegexp)
30 42
 
31 43
 	// DigestRegexp matches valid digests.
32
-	DigestRegexp = regexp.MustCompile(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
44
+	DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
33 45
 
34 46
 	// anchoredDigestRegexp matches valid digests, anchored at the start and
35 47
 	// end of the matched string.
36
-	anchoredDigestRegexp = regexp.MustCompile(`^` + DigestRegexp.String() + `$`)
48
+	anchoredDigestRegexp = anchored(DigestRegexp)
37 49
 
38 50
 	// NameRegexp is the format for the name component of references. The
39 51
 	// regexp has capturing groups for the hostname and name part omitting
40 52
 	// the seperating forward slash from either.
41
-	NameRegexp = regexp.MustCompile(`(?:` + hostnameRegexp.String() + `/)?` + nameRegexp.String())
53
+	NameRegexp = expression(
54
+		optional(hostnameRegexp, literal(`/`)),
55
+		nameComponentRegexp,
56
+		optional(repeated(literal(`/`), nameComponentRegexp)))
42 57
 
43
-	// ReferenceRegexp is the full supported format of a reference. The
44
-	// regexp has capturing groups for name, tag, and digest components.
45
-	ReferenceRegexp = regexp.MustCompile(`^((?:` + hostnameRegexp.String() + `/)?` + nameRegexp.String() + `)(?:[:](` + TagRegexp.String() + `))?(?:[@](` + DigestRegexp.String() + `))?$`)
58
+	// anchoredNameRegexp is used to parse a name value, capturing the
59
+	// hostname and trailing components.
60
+	anchoredNameRegexp = anchored(
61
+		optional(capture(hostnameRegexp), literal(`/`)),
62
+		capture(nameComponentRegexp,
63
+			optional(repeated(literal(`/`), nameComponentRegexp))))
46 64
 
47
-	// anchoredNameRegexp is used to parse a name value, capturing hostname
48
-	anchoredNameRegexp = regexp.MustCompile(`^(?:(` + hostnameRegexp.String() + `)/)?(` + nameRegexp.String() + `)$`)
65
+	// ReferenceRegexp is the full supported format of a reference. The regexp
66
+	// is anchored and has capturing groups for name, tag, and digest
67
+	// components.
68
+	ReferenceRegexp = anchored(capture(NameRegexp),
69
+		optional(literal(":"), capture(TagRegexp)),
70
+		optional(literal("@"), capture(DigestRegexp)))
49 71
 )
72
+
73
+// match compiles the string to a regular expression.
74
+var match = regexp.MustCompile
75
+
76
+// literal compiles s into a literal regular expression, escaping any regexp
77
+// reserved characters.
78
+func literal(s string) *regexp.Regexp {
79
+	re := match(regexp.QuoteMeta(s))
80
+
81
+	if _, complete := re.LiteralPrefix(); !complete {
82
+		panic("must be a literal")
83
+	}
84
+
85
+	return re
86
+}
87
+
88
+// expression defines a full expression, where each regular expression must
89
+// follow the previous.
90
+func expression(res ...*regexp.Regexp) *regexp.Regexp {
91
+	var s string
92
+	for _, re := range res {
93
+		s += re.String()
94
+	}
95
+
96
+	return match(s)
97
+}
98
+
99
+// optional wraps the expression in a non-capturing group and makes the
100
+// production optional.
101
+func optional(res ...*regexp.Regexp) *regexp.Regexp {
102
+	return match(group(expression(res...)).String() + `?`)
103
+}
104
+
105
+// repeated wraps the regexp in a non-capturing group to get one or more
106
+// matches.
107
+func repeated(res ...*regexp.Regexp) *regexp.Regexp {
108
+	return match(group(expression(res...)).String() + `+`)
109
+}
110
+
111
+// group wraps the regexp in a non-capturing group.
112
+func group(res ...*regexp.Regexp) *regexp.Regexp {
113
+	return match(`(?:` + expression(res...).String() + `)`)
114
+}
115
+
116
+// capture wraps the expression in a capturing group.
117
+func capture(res ...*regexp.Regexp) *regexp.Regexp {
118
+	return match(`(` + expression(res...).String() + `)`)
119
+}
120
+
121
+// anchored anchors the regular expression by adding start and end delimiters.
122
+func anchored(res ...*regexp.Regexp) *regexp.Regexp {
123
+	return match(`^` + expression(res...).String() + `$`)
124
+}
... ...
@@ -2,8 +2,6 @@ package distribution
2 2
 
3 3
 import (
4 4
 	"github.com/docker/distribution/context"
5
-	"github.com/docker/distribution/digest"
6
-	"github.com/docker/distribution/manifest/schema1"
7 5
 )
8 6
 
9 7
 // Scope defines the set of items that match a namespace.
... ...
@@ -44,7 +42,9 @@ type Namespace interface {
44 44
 }
45 45
 
46 46
 // ManifestServiceOption is a function argument for Manifest Service methods
47
-type ManifestServiceOption func(ManifestService) error
47
+type ManifestServiceOption interface {
48
+	Apply(ManifestService) error
49
+}
48 50
 
49 51
 // Repository is a named collection of manifests and layers.
50 52
 type Repository interface {
... ...
@@ -62,59 +62,10 @@ type Repository interface {
62 62
 	// be a BlobService for use with clients. This will allow such
63 63
 	// implementations to avoid implementing ServeBlob.
64 64
 
65
-	// Signatures returns a reference to this repository's signatures service.
66
-	Signatures() SignatureService
65
+	// Tags returns a reference to this repositories tag service
66
+	Tags(ctx context.Context) TagService
67 67
 }
68 68
 
69 69
 // TODO(stevvooe): Must add close methods to all these. May want to change the
70 70
 // way instances are created to better reflect internal dependency
71 71
 // relationships.
72
-
73
-// ManifestService provides operations on image manifests.
74
-type ManifestService interface {
75
-	// Exists returns true if the manifest exists.
76
-	Exists(dgst digest.Digest) (bool, error)
77
-
78
-	// Get retrieves the identified by the digest, if it exists.
79
-	Get(dgst digest.Digest) (*schema1.SignedManifest, error)
80
-
81
-	// Delete removes the manifest, if it exists.
82
-	Delete(dgst digest.Digest) error
83
-
84
-	// Put creates or updates the manifest.
85
-	Put(manifest *schema1.SignedManifest) error
86
-
87
-	// TODO(stevvooe): The methods after this message should be moved to a
88
-	// discrete TagService, per active proposals.
89
-
90
-	// Tags lists the tags under the named repository.
91
-	Tags() ([]string, error)
92
-
93
-	// ExistsByTag returns true if the manifest exists.
94
-	ExistsByTag(tag string) (bool, error)
95
-
96
-	// GetByTag retrieves the named manifest, if it exists.
97
-	GetByTag(tag string, options ...ManifestServiceOption) (*schema1.SignedManifest, error)
98
-
99
-	// TODO(stevvooe): There are several changes that need to be done to this
100
-	// interface:
101
-	//
102
-	//	1. Allow explicit tagging with Tag(digest digest.Digest, tag string)
103
-	//	2. Support reading tags with a re-entrant reader to avoid large
104
-	//       allocations in the registry.
105
-	//	3. Long-term: Provide All() method that lets one scroll through all of
106
-	//       the manifest entries.
107
-	//	4. Long-term: break out concept of signing from manifests. This is
108
-	//       really a part of the distribution sprint.
109
-	//	5. Long-term: Manifest should be an interface. This code shouldn't
110
-	//       really be concerned with the storage format.
111
-}
112
-
113
-// SignatureService provides operations on signatures.
114
-type SignatureService interface {
115
-	// Get retrieves all of the signature blobs for the specified digest.
116
-	Get(dgst digest.Digest) ([][]byte, error)
117
-
118
-	// Put stores the signature for the provided digest.
119
-	Put(dgst digest.Digest, signatures ...[]byte) error
120
-}
... ...
@@ -25,7 +25,8 @@ func (ec ErrorCode) ErrorCode() ErrorCode {
25 25
 
26 26
 // Error returns the ID/Value
27 27
 func (ec ErrorCode) Error() string {
28
-	return ec.Descriptor().Value
28
+	// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
29
+	return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
29 30
 }
30 31
 
31 32
 // Descriptor returns the descriptor for the error code.
... ...
@@ -104,9 +105,7 @@ func (e Error) ErrorCode() ErrorCode {
104 104
 
105 105
 // Error returns a human readable representation of the error.
106 106
 func (e Error) Error() string {
107
-	return fmt.Sprintf("%s: %s",
108
-		strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
109
-		e.Message)
107
+	return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
110 108
 }
111 109
 
112 110
 // WithDetail will return a new Error, based on the current one, but with
... ...
@@ -495,7 +495,7 @@ var routeDescriptors = []RouteDescriptor{
495 495
 		Methods: []MethodDescriptor{
496 496
 			{
497 497
 				Method:      "GET",
498
-				Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest.",
498
+				Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest. A `HEAD` request can also be issued to this endpoint to obtain resource information without receiving all data.",
499 499
 				Requests: []RequestDescriptor{
500 500
 					{
501 501
 						Headers: []ParameterDescriptor{
... ...
@@ -204,7 +204,9 @@ func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
204 204
 		routeURL.Path = routeURL.Path[1:]
205 205
 	}
206 206
 
207
-	return cr.root.ResolveReference(routeURL), nil
207
+	url := cr.root.ResolveReference(routeURL)
208
+	url.Scheme = cr.root.Scheme
209
+	return url, nil
208 210
 }
209 211
 
210 212
 // appendValuesURL appends the parameters to the url.
... ...
@@ -240,7 +240,8 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon
240 240
 	defer resp.Body.Close()
241 241
 
242 242
 	if !client.SuccessStatus(resp.StatusCode) {
243
-		return nil, fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
243
+		err := client.HandleErrorResponse(resp)
244
+		return nil, err
244 245
 	}
245 246
 
246 247
 	decoder := json.NewDecoder(resp.Body)
... ...
@@ -33,7 +33,7 @@ func (hbu *httpBlobUpload) handleErrorResponse(resp *http.Response) error {
33 33
 	if resp.StatusCode == http.StatusNotFound {
34 34
 		return distribution.ErrBlobUploadUnknown
35 35
 	}
36
-	return handleErrorResponse(resp)
36
+	return HandleErrorResponse(resp)
37 37
 }
38 38
 
39 39
 func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) {
... ...
@@ -47,7 +47,11 @@ func parseHTTPErrorResponse(r io.Reader) error {
47 47
 	return errors
48 48
 }
49 49
 
50
-func handleErrorResponse(resp *http.Response) error {
50
+// HandleErrorResponse returns error parsed from HTTP response for an
51
+// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An
52
+// UnexpectedHTTPStatusError returned for response code outside of expected
53
+// range.
54
+func HandleErrorResponse(resp *http.Response) error {
51 55
 	if resp.StatusCode == 401 {
52 56
 		err := parseHTTPErrorResponse(resp.Body)
53 57
 		if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
... ...
@@ -3,6 +3,7 @@ package client
3 3
 import (
4 4
 	"bytes"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"io"
8 9
 	"io/ioutil"
... ...
@@ -14,7 +15,6 @@ import (
14 14
 	"github.com/docker/distribution"
15 15
 	"github.com/docker/distribution/context"
16 16
 	"github.com/docker/distribution/digest"
17
-	"github.com/docker/distribution/manifest/schema1"
18 17
 	"github.com/docker/distribution/reference"
19 18
 	"github.com/docker/distribution/registry/api/v2"
20 19
 	"github.com/docker/distribution/registry/client/transport"
... ...
@@ -91,7 +91,7 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
91 91
 			returnErr = io.EOF
92 92
 		}
93 93
 	} else {
94
-		return 0, handleErrorResponse(resp)
94
+		return 0, HandleErrorResponse(resp)
95 95
 	}
96 96
 
97 97
 	return numFilled, returnErr
... ...
@@ -156,74 +156,151 @@ func (r *repository) Manifests(ctx context.Context, options ...distribution.Mani
156 156
 	}, nil
157 157
 }
158 158
 
159
-func (r *repository) Signatures() distribution.SignatureService {
160
-	ms, _ := r.Manifests(r.context)
161
-	return &signatures{
162
-		manifests: ms,
159
+func (r *repository) Tags(ctx context.Context) distribution.TagService {
160
+	return &tags{
161
+		client:  r.client,
162
+		ub:      r.ub,
163
+		context: r.context,
164
+		name:    r.Name(),
163 165
 	}
164 166
 }
165 167
 
166
-type signatures struct {
167
-	manifests distribution.ManifestService
168
-}
169
-
170
-func (s *signatures) Get(dgst digest.Digest) ([][]byte, error) {
171
-	m, err := s.manifests.Get(dgst)
172
-	if err != nil {
173
-		return nil, err
174
-	}
175
-	return m.Signatures()
168
+// tags implements remote tagging operations.
169
+type tags struct {
170
+	client  *http.Client
171
+	ub      *v2.URLBuilder
172
+	context context.Context
173
+	name    string
176 174
 }
177 175
 
178
-func (s *signatures) Put(dgst digest.Digest, signatures ...[]byte) error {
179
-	panic("not implemented")
180
-}
176
+// All returns all tags
177
+func (t *tags) All(ctx context.Context) ([]string, error) {
178
+	var tags []string
181 179
 
182
-type manifests struct {
183
-	name   string
184
-	ub     *v2.URLBuilder
185
-	client *http.Client
186
-	etags  map[string]string
187
-}
188
-
189
-func (ms *manifests) Tags() ([]string, error) {
190
-	u, err := ms.ub.BuildTagsURL(ms.name)
180
+	u, err := t.ub.BuildTagsURL(t.name)
191 181
 	if err != nil {
192
-		return nil, err
182
+		return tags, err
193 183
 	}
194 184
 
195
-	resp, err := ms.client.Get(u)
185
+	resp, err := t.client.Get(u)
196 186
 	if err != nil {
197
-		return nil, err
187
+		return tags, err
198 188
 	}
199 189
 	defer resp.Body.Close()
200 190
 
201 191
 	if SuccessStatus(resp.StatusCode) {
202 192
 		b, err := ioutil.ReadAll(resp.Body)
203 193
 		if err != nil {
204
-			return nil, err
194
+			return tags, err
205 195
 		}
206 196
 
207 197
 		tagsResponse := struct {
208 198
 			Tags []string `json:"tags"`
209 199
 		}{}
210 200
 		if err := json.Unmarshal(b, &tagsResponse); err != nil {
211
-			return nil, err
201
+			return tags, err
212 202
 		}
203
+		tags = tagsResponse.Tags
204
+		return tags, nil
205
+	}
206
+	return tags, HandleErrorResponse(resp)
207
+}
213 208
 
214
-		return tagsResponse.Tags, nil
209
+func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) {
210
+	desc := distribution.Descriptor{}
211
+	headers := response.Header
212
+
213
+	ctHeader := headers.Get("Content-Type")
214
+	if ctHeader == "" {
215
+		return distribution.Descriptor{}, errors.New("missing or empty Content-Type header")
216
+	}
217
+	desc.MediaType = ctHeader
218
+
219
+	digestHeader := headers.Get("Docker-Content-Digest")
220
+	if digestHeader == "" {
221
+		bytes, err := ioutil.ReadAll(response.Body)
222
+		if err != nil {
223
+			return distribution.Descriptor{}, err
224
+		}
225
+		_, desc, err := distribution.UnmarshalManifest(ctHeader, bytes)
226
+		if err != nil {
227
+			return distribution.Descriptor{}, err
228
+		}
229
+		return desc, nil
230
+	}
231
+
232
+	dgst, err := digest.ParseDigest(digestHeader)
233
+	if err != nil {
234
+		return distribution.Descriptor{}, err
235
+	}
236
+	desc.Digest = dgst
237
+
238
+	lengthHeader := headers.Get("Content-Length")
239
+	if lengthHeader == "" {
240
+		return distribution.Descriptor{}, errors.New("missing or empty Content-Length header")
241
+	}
242
+	length, err := strconv.ParseInt(lengthHeader, 10, 64)
243
+	if err != nil {
244
+		return distribution.Descriptor{}, err
245
+	}
246
+	desc.Size = length
247
+
248
+	return desc, nil
249
+
250
+}
251
+
252
+// Get issues a HEAD request for a Manifest against its named endpoint in order
253
+// to construct a descriptor for the tag.  If the registry doesn't support HEADing
254
+// a manifest, fallback to GET.
255
+func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
256
+	u, err := t.ub.BuildManifestURL(t.name, tag)
257
+	if err != nil {
258
+		return distribution.Descriptor{}, err
259
+	}
260
+	var attempts int
261
+	resp, err := t.client.Head(u)
262
+
263
+check:
264
+	if err != nil {
265
+		return distribution.Descriptor{}, err
266
+	}
267
+
268
+	switch {
269
+	case resp.StatusCode >= 200 && resp.StatusCode < 400:
270
+		return descriptorFromResponse(resp)
271
+	case resp.StatusCode == http.StatusMethodNotAllowed:
272
+		resp, err = t.client.Get(u)
273
+		attempts++
274
+		if attempts > 1 {
275
+			return distribution.Descriptor{}, err
276
+		}
277
+		goto check
278
+	default:
279
+		return distribution.Descriptor{}, HandleErrorResponse(resp)
215 280
 	}
216
-	return nil, handleErrorResponse(resp)
217 281
 }
218 282
 
219
-func (ms *manifests) Exists(dgst digest.Digest) (bool, error) {
220
-	// Call by Tag endpoint since the API uses the same
221
-	// URL endpoint for tags and digests.
222
-	return ms.ExistsByTag(dgst.String())
283
+func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
284
+	panic("not implemented")
285
+}
286
+
287
+func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
288
+	panic("not implemented")
223 289
 }
224 290
 
225
-func (ms *manifests) ExistsByTag(tag string) (bool, error) {
226
-	u, err := ms.ub.BuildManifestURL(ms.name, tag)
291
+func (t *tags) Untag(ctx context.Context, tag string) error {
292
+	panic("not implemented")
293
+}
294
+
295
+type manifests struct {
296
+	name   string
297
+	ub     *v2.URLBuilder
298
+	client *http.Client
299
+	etags  map[string]string
300
+}
301
+
302
+func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
303
+	u, err := ms.ub.BuildManifestURL(ms.name, dgst.String())
227 304
 	if err != nil {
228 305
 		return false, err
229 306
 	}
... ...
@@ -238,49 +315,66 @@ func (ms *manifests) ExistsByTag(tag string) (bool, error) {
238 238
 	} else if resp.StatusCode == http.StatusNotFound {
239 239
 		return false, nil
240 240
 	}
241
-	return false, handleErrorResponse(resp)
242
-}
243
-
244
-func (ms *manifests) Get(dgst digest.Digest) (*schema1.SignedManifest, error) {
245
-	// Call by Tag endpoint since the API uses the same
246
-	// URL endpoint for tags and digests.
247
-	return ms.GetByTag(dgst.String())
241
+	return false, HandleErrorResponse(resp)
248 242
 }
249 243
 
250
-// AddEtagToTag allows a client to supply an eTag to GetByTag which will be
244
+// AddEtagToTag allows a client to supply an eTag to Get which will be
251 245
 // used for a conditional HTTP request.  If the eTag matches, a nil manifest
252
-// and nil error will be returned. etag is automatically quoted when added to
253
-// this map.
246
+// and ErrManifestNotModified error will be returned. etag is automatically
247
+// quoted when added to this map.
254 248
 func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
255
-	return func(ms distribution.ManifestService) error {
256
-		if ms, ok := ms.(*manifests); ok {
257
-			ms.etags[tag] = fmt.Sprintf(`"%s"`, etag)
258
-			return nil
259
-		}
260
-		return fmt.Errorf("etag options is a client-only option")
249
+	return etagOption{tag, etag}
250
+}
251
+
252
+type etagOption struct{ tag, etag string }
253
+
254
+func (o etagOption) Apply(ms distribution.ManifestService) error {
255
+	if ms, ok := ms.(*manifests); ok {
256
+		ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag)
257
+		return nil
261 258
 	}
259
+	return fmt.Errorf("etag options is a client-only option")
262 260
 }
263 261
 
264
-func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*schema1.SignedManifest, error) {
262
+func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
263
+
264
+	var tag string
265 265
 	for _, option := range options {
266
-		err := option(ms)
267
-		if err != nil {
268
-			return nil, err
266
+		if opt, ok := option.(withTagOption); ok {
267
+			tag = opt.tag
268
+		} else {
269
+			err := option.Apply(ms)
270
+			if err != nil {
271
+				return nil, err
272
+			}
269 273
 		}
270 274
 	}
271 275
 
272
-	u, err := ms.ub.BuildManifestURL(ms.name, tag)
276
+	var ref string
277
+	if tag != "" {
278
+		ref = tag
279
+	} else {
280
+		ref = dgst.String()
281
+	}
282
+
283
+	u, err := ms.ub.BuildManifestURL(ms.name, ref)
273 284
 	if err != nil {
274 285
 		return nil, err
275 286
 	}
287
+
276 288
 	req, err := http.NewRequest("GET", u, nil)
277 289
 	if err != nil {
278 290
 		return nil, err
279 291
 	}
280 292
 
281
-	if _, ok := ms.etags[tag]; ok {
282
-		req.Header.Set("If-None-Match", ms.etags[tag])
293
+	for _, t := range distribution.ManifestMediaTypes() {
294
+		req.Header.Add("Accept", t)
295
+	}
296
+
297
+	if _, ok := ms.etags[ref]; ok {
298
+		req.Header.Set("If-None-Match", ms.etags[ref])
283 299
 	}
300
+
284 301
 	resp, err := ms.client.Do(req)
285 302
 	if err != nil {
286 303
 		return nil, err
... ...
@@ -289,44 +383,89 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
289 289
 	if resp.StatusCode == http.StatusNotModified {
290 290
 		return nil, distribution.ErrManifestNotModified
291 291
 	} else if SuccessStatus(resp.StatusCode) {
292
-		var sm schema1.SignedManifest
293
-		decoder := json.NewDecoder(resp.Body)
292
+		mt := resp.Header.Get("Content-Type")
293
+		body, err := ioutil.ReadAll(resp.Body)
294 294
 
295
-		if err := decoder.Decode(&sm); err != nil {
295
+		if err != nil {
296
+			return nil, err
297
+		}
298
+		m, _, err := distribution.UnmarshalManifest(mt, body)
299
+		if err != nil {
296 300
 			return nil, err
297 301
 		}
298
-		return &sm, nil
302
+		return m, nil
303
+	}
304
+	return nil, HandleErrorResponse(resp)
305
+}
306
+
307
+// WithTag allows a tag to be passed into Put which enables the client
308
+// to build a correct URL.
309
+func WithTag(tag string) distribution.ManifestServiceOption {
310
+	return withTagOption{tag}
311
+}
312
+
313
+type withTagOption struct{ tag string }
314
+
315
+func (o withTagOption) Apply(m distribution.ManifestService) error {
316
+	if _, ok := m.(*manifests); ok {
317
+		return nil
299 318
 	}
300
-	return nil, handleErrorResponse(resp)
319
+	return fmt.Errorf("withTagOption is a client-only option")
301 320
 }
302 321
 
303
-func (ms *manifests) Put(m *schema1.SignedManifest) error {
304
-	manifestURL, err := ms.ub.BuildManifestURL(ms.name, m.Tag)
322
+// Put puts a manifest.  A tag can be specified using an options parameter which uses some shared state to hold the
323
+// tag name in order to build the correct upload URL.  This state is written and read under a lock.
324
+func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
325
+	var tag string
326
+
327
+	for _, option := range options {
328
+		if opt, ok := option.(withTagOption); ok {
329
+			tag = opt.tag
330
+		} else {
331
+			err := option.Apply(ms)
332
+			if err != nil {
333
+				return "", err
334
+			}
335
+		}
336
+	}
337
+
338
+	manifestURL, err := ms.ub.BuildManifestURL(ms.name, tag)
305 339
 	if err != nil {
306
-		return err
340
+		return "", err
307 341
 	}
308 342
 
309
-	// todo(richardscothern): do something with options here when they become applicable
343
+	mediaType, p, err := m.Payload()
344
+	if err != nil {
345
+		return "", err
346
+	}
310 347
 
311
-	putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(m.Raw))
348
+	putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(p))
312 349
 	if err != nil {
313
-		return err
350
+		return "", err
314 351
 	}
315 352
 
353
+	putRequest.Header.Set("Content-Type", mediaType)
354
+
316 355
 	resp, err := ms.client.Do(putRequest)
317 356
 	if err != nil {
318
-		return err
357
+		return "", err
319 358
 	}
320 359
 	defer resp.Body.Close()
321 360
 
322 361
 	if SuccessStatus(resp.StatusCode) {
323
-		// TODO(dmcgowan): make use of digest header
324
-		return nil
362
+		dgstHeader := resp.Header.Get("Docker-Content-Digest")
363
+		dgst, err := digest.ParseDigest(dgstHeader)
364
+		if err != nil {
365
+			return "", err
366
+		}
367
+
368
+		return dgst, nil
325 369
 	}
326
-	return handleErrorResponse(resp)
370
+
371
+	return "", HandleErrorResponse(resp)
327 372
 }
328 373
 
329
-func (ms *manifests) Delete(dgst digest.Digest) error {
374
+func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
330 375
 	u, err := ms.ub.BuildManifestURL(ms.name, dgst.String())
331 376
 	if err != nil {
332 377
 		return err
... ...
@@ -345,9 +484,14 @@ func (ms *manifests) Delete(dgst digest.Digest) error {
345 345
 	if SuccessStatus(resp.StatusCode) {
346 346
 		return nil
347 347
 	}
348
-	return handleErrorResponse(resp)
348
+	return HandleErrorResponse(resp)
349 349
 }
350 350
 
351
+// todo(richardscothern): Restore interface and implementation with merge of #1050
352
+/*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
353
+	panic("not supported")
354
+}*/
355
+
351 356
 type blobs struct {
352 357
 	name   string
353 358
 	ub     *v2.URLBuilder
... ...
@@ -377,11 +521,7 @@ func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Des
377 377
 }
378 378
 
379 379
 func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
380
-	desc, err := bs.Stat(ctx, dgst)
381
-	if err != nil {
382
-		return nil, err
383
-	}
384
-	reader, err := bs.Open(ctx, desc.Digest)
380
+	reader, err := bs.Open(ctx, dgst)
385 381
 	if err != nil {
386 382
 		return nil, err
387 383
 	}
... ...
@@ -401,7 +541,7 @@ func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.Rea
401 401
 			if resp.StatusCode == http.StatusNotFound {
402 402
 				return distribution.ErrBlobUnknown
403 403
 			}
404
-			return handleErrorResponse(resp)
404
+			return HandleErrorResponse(resp)
405 405
 		}), nil
406 406
 }
407 407
 
... ...
@@ -457,7 +597,7 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
457 457
 			location:  location,
458 458
 		}, nil
459 459
 	}
460
-	return nil, handleErrorResponse(resp)
460
+	return nil, HandleErrorResponse(resp)
461 461
 }
462 462
 
463 463
 func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
... ...
@@ -488,6 +628,10 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
488 488
 
489 489
 	if SuccessStatus(resp.StatusCode) {
490 490
 		lengthHeader := resp.Header.Get("Content-Length")
491
+		if lengthHeader == "" {
492
+			return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u)
493
+		}
494
+
491 495
 		length, err := strconv.ParseInt(lengthHeader, 10, 64)
492 496
 		if err != nil {
493 497
 			return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err)
... ...
@@ -501,7 +645,7 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
501 501
 	} else if resp.StatusCode == http.StatusNotFound {
502 502
 		return distribution.Descriptor{}, distribution.ErrBlobUnknown
503 503
 	}
504
-	return distribution.Descriptor{}, handleErrorResponse(resp)
504
+	return distribution.Descriptor{}, HandleErrorResponse(resp)
505 505
 }
506 506
 
507 507
 func buildCatalogValues(maxEntries int, last string) url.Values {
... ...
@@ -538,7 +682,7 @@ func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
538 538
 	if SuccessStatus(resp.StatusCode) {
539 539
 		return nil
540 540
 	}
541
-	return handleErrorResponse(resp)
541
+	return HandleErrorResponse(resp)
542 542
 }
543 543
 
544 544
 func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
545 545
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package distribution
1
+
2
+import (
3
+	"github.com/docker/distribution/context"
4
+)
5
+
6
+// TagService provides access to information about tagged objects.
7
+type TagService interface {
8
+	// Get retrieves the descriptor identified by the tag. Some
9
+	// implementations may differentiate between "trusted" tags and
10
+	// "untrusted" tags. If a tag is "untrusted", the mapping will be returned
11
+	// as an ErrTagUntrusted error, with the target descriptor.
12
+	Get(ctx context.Context, tag string) (Descriptor, error)
13
+
14
+	// Tag associates the tag with the provided descriptor, updating the
15
+	// current association, if needed.
16
+	Tag(ctx context.Context, tag string, desc Descriptor) error
17
+
18
+	// Untag removes the given tag association
19
+	Untag(ctx context.Context, tag string) error
20
+
21
+	// All returns the set of tags managed by this tag service
22
+	All(ctx context.Context) ([]string, error)
23
+
24
+	// Lookup returns the set of tags referencing the given digest.
25
+	Lookup(ctx context.Context, digest Descriptor) ([]string, error)
26
+}