Browse code

Vendor updated github.com/docker/distribution

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2015/12/09 03:47:48
Showing 36 changed files
... ...
@@ -43,7 +43,7 @@ clone git github.com/hashicorp/consul v0.5.2
43 43
 clone git github.com/boltdb/bolt v1.1.0
44 44
 
45 45
 # get graph and distribution packages
46
-clone git github.com/docker/distribution 568bf038af6d65b376165d02886b1c7fcaef1f61
46
+clone git github.com/docker/distribution a7ae88da459b98b481a245e5b1750134724ac67d
47 47
 clone git github.com/vbatts/tar-split v0.9.11
48 48
 
49 49
 # get desired notary commit, might also need to be updated in Dockerfile
... ...
@@ -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
+}