spec: fetch manifests by tag or digest
Manifests are now fetched by a field called "reference", which may be a tag or a digest. When using digests to reference a manifest, the data is immutable. The routes and specification have been updated to allow this. There are a few caveats to this approach: 1. It may be problematic to rely on data format to differentiate between a tag and a digest. Currently, they are disjoint but there may modifications on either side that break this guarantee. 2. The caching characteristics of returned content are very different for digest versus tag-based references. Digest urls can be cached forever while tag urls cannot. Both of these are minimal caveats that we can live with in the future. Signed-off-by: Stephen J Day <stephen.day@docker.com>master
							parent
							
								
									0ecb468a33
								
							
						
					
					
						commit
						f46a1b73e8
					
				| 
						 | 
				
			
			@ -79,6 +79,13 @@ var (
 | 
			
		|||
		Format:      "<uuid>",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	digestHeader = ParameterDescriptor{
 | 
			
		||||
		Name:        "Docker-Content-Digest",
 | 
			
		||||
		Description: "Digest of the targeted content for the request.",
 | 
			
		||||
		Type:        "digest",
 | 
			
		||||
		Format:      "<digest>",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unauthorizedResponse = ResponseDescriptor{
 | 
			
		||||
		Description: "The client does not have access to the repository.",
 | 
			
		||||
		StatusCode:  http.StatusUnauthorized,
 | 
			
		||||
| 
						 | 
				
			
			@ -454,13 +461,13 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Name:        RouteNameManifest,
 | 
			
		||||
		Path:        "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{tag:" + TagNameRegexp.String() + "}",
 | 
			
		||||
		Path:        "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + digest.DigestRegexp.String() + "}",
 | 
			
		||||
		Entity:      "Manifest",
 | 
			
		||||
		Description: "Create, update and retrieve manifests.",
 | 
			
		||||
		Methods: []MethodDescriptor{
 | 
			
		||||
			{
 | 
			
		||||
				Method:      "GET",
 | 
			
		||||
				Description: "Fetch the manifest identified by `name` and `tag`.",
 | 
			
		||||
				Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest.",
 | 
			
		||||
				Requests: []RequestDescriptor{
 | 
			
		||||
					{
 | 
			
		||||
						Headers: []ParameterDescriptor{
 | 
			
		||||
| 
						 | 
				
			
			@ -473,8 +480,11 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
						},
 | 
			
		||||
						Successes: []ResponseDescriptor{
 | 
			
		||||
							{
 | 
			
		||||
								Description: "The manifest idenfied by `name` and `tag`. The contents can be used to identify and resolve resources required to run the specified image.",
 | 
			
		||||
								Description: "The manifest idenfied by `name` and `reference`. The contents can be used to identify and resolve resources required to run the specified image.",
 | 
			
		||||
								StatusCode:  http.StatusOK,
 | 
			
		||||
								Headers: []ParameterDescriptor{
 | 
			
		||||
									digestHeader,
 | 
			
		||||
								},
 | 
			
		||||
								Body: BodyDescriptor{
 | 
			
		||||
									ContentType: "application/json; charset=utf-8",
 | 
			
		||||
									Format:      manifestBody,
 | 
			
		||||
| 
						 | 
				
			
			@ -483,7 +493,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
						},
 | 
			
		||||
						Failures: []ResponseDescriptor{
 | 
			
		||||
							{
 | 
			
		||||
								Description: "The name or tag was invalid.",
 | 
			
		||||
								Description: "The name or reference was invalid.",
 | 
			
		||||
								StatusCode:  http.StatusBadRequest,
 | 
			
		||||
								ErrorCodes: []ErrorCode{
 | 
			
		||||
									ErrorCodeNameInvalid,
 | 
			
		||||
| 
						 | 
				
			
			@ -523,7 +533,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Method:      "PUT",
 | 
			
		||||
				Description: "Put the manifest identified by `name` and `tag`.",
 | 
			
		||||
				Description: "Put the manifest identified by `name` and `reference` where `reference` can be a tag or digest.",
 | 
			
		||||
				Requests: []RequestDescriptor{
 | 
			
		||||
					{
 | 
			
		||||
						Headers: []ParameterDescriptor{
 | 
			
		||||
| 
						 | 
				
			
			@ -550,6 +560,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
										Format:      "<url>",
 | 
			
		||||
									},
 | 
			
		||||
									contentLengthZeroHeader,
 | 
			
		||||
									digestHeader,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
| 
						 | 
				
			
			@ -628,7 +639,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Method:      "DELETE",
 | 
			
		||||
				Description: "Delete the manifest identified by `name` and `tag`.",
 | 
			
		||||
				Description: "Delete the manifest identified by `name` and `reference` where `reference` can be a tag or digest.",
 | 
			
		||||
				Requests: []RequestDescriptor{
 | 
			
		||||
					{
 | 
			
		||||
						Headers: []ParameterDescriptor{
 | 
			
		||||
| 
						 | 
				
			
			@ -729,6 +740,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
										Description: "The length of the requested blob content.",
 | 
			
		||||
										Format:      "<length>",
 | 
			
		||||
									},
 | 
			
		||||
									digestHeader,
 | 
			
		||||
								},
 | 
			
		||||
								Body: BodyDescriptor{
 | 
			
		||||
									ContentType: "application/octet-stream",
 | 
			
		||||
| 
						 | 
				
			
			@ -745,6 +757,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
										Description: "The location where the layer should be accessible.",
 | 
			
		||||
										Format:      "<blob location>",
 | 
			
		||||
									},
 | 
			
		||||
									digestHeader,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
| 
						 | 
				
			
			@ -1193,6 +1206,7 @@ var routeDescriptors = []RouteDescriptor{
 | 
			
		|||
										Format:      "<length of chunk>",
 | 
			
		||||
										Description: "Length of the chunk being uploaded, corresponding the length of the request body.",
 | 
			
		||||
									},
 | 
			
		||||
									digestHeader,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
| 
						 | 
				
			
			@ -1312,6 +1326,13 @@ var errorDescriptors = []ErrorDescriptor{
 | 
			
		|||
		Description: `Generic error returned when the error does not have an
 | 
			
		||||
		API classification.`,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Code:    ErrorCodeUnsupported,
 | 
			
		||||
		Value:   "UNSUPPORTED",
 | 
			
		||||
		Message: "The operation is unsupported.",
 | 
			
		||||
		Description: `The operation was unsupported due to a missing
 | 
			
		||||
		implementation or invalid set of parameters.`,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		Code:    ErrorCodeUnauthorized,
 | 
			
		||||
		Value:   "UNAUTHORIZED",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,9 @@ const (
 | 
			
		|||
	// ErrorCodeUnknown is a catch-all for errors not defined below.
 | 
			
		||||
	ErrorCodeUnknown ErrorCode = iota
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeUnsupported is returned when an operation is not supported.
 | 
			
		||||
	ErrorCodeUnsupported
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeUnauthorized is returned if a request is not authorized.
 | 
			
		||||
	ErrorCodeUnauthorized
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,16 +39,24 @@ func TestRouter(t *testing.T) {
 | 
			
		|||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/manifests/bar",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo",
 | 
			
		||||
				"tag":  "bar",
 | 
			
		||||
				"name":      "foo",
 | 
			
		||||
				"reference": "bar",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/bar/manifests/tag",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo/bar",
 | 
			
		||||
				"tag":  "tag",
 | 
			
		||||
				"name":      "foo/bar",
 | 
			
		||||
				"reference": "tag",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/bar/manifests/sha256:abcdef01234567890",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name":      "foo/bar",
 | 
			
		||||
				"reference": "sha256:abcdef01234567890",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -112,8 +120,8 @@ func TestRouter(t *testing.T) {
 | 
			
		|||
			RouteName:  RouteNameManifest,
 | 
			
		||||
			RequestURI: "/v2/foo/bar/manifests/manifests/tags",
 | 
			
		||||
			Vars: map[string]string{
 | 
			
		||||
				"name": "foo/bar/manifests",
 | 
			
		||||
				"tag":  "tags",
 | 
			
		||||
				"name":      "foo/bar/manifests",
 | 
			
		||||
				"reference": "tags",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -107,11 +107,12 @@ func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
 | 
			
		|||
	return tagsURL.String(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildManifestURL constructs a url for the manifest identified by name and tag.
 | 
			
		||||
func (ub *URLBuilder) BuildManifestURL(name, tag string) (string, error) {
 | 
			
		||||
// BuildManifestURL constructs a url for the manifest identified by name and
 | 
			
		||||
// reference. The argument reference may be either a tag or digest.
 | 
			
		||||
func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
 | 
			
		||||
	route := ub.cloneRoute(RouteNameManifest)
 | 
			
		||||
 | 
			
		||||
	manifestURL, err := route.URL("name", name, "tag", tag)
 | 
			
		||||
	manifestURL, err := route.URL("name", name, "reference", reference)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue