From 4b813b38476089abc347dd387e4d576530dd1e23 Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Fri, 27 Feb 2015 02:23:50 +0000 Subject: [PATCH] Add ability to refer to image by name + digest Add ability to refer to an image by repository name and digest using the format repository@digest. Works for pull, push, run, build, and rmi. Signed-off-by: Andy Goldstein --- docs/session_v2.go | 40 +++++++++++++++++++++------------------- docs/v2/regexp.go | 3 +++ docs/v2/routes.go | 8 ++++---- docs/v2/routes_test.go | 12 ++++++------ docs/v2/urls.go | 6 +++--- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/docs/session_v2.go b/docs/session_v2.go index da5371d8..c5bee11b 100644 --- a/docs/session_v2.go +++ b/docs/session_v2.go @@ -12,6 +12,8 @@ import ( "github.com/docker/docker/utils" ) +const DockerDigestHeader = "Docker-Content-Digest" + func getV2Builder(e *Endpoint) *v2.URLBuilder { if e.URLBuilder == nil { e.URLBuilder = v2.NewURLBuilder(e.URL) @@ -63,10 +65,10 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo // 1.c) if anything else, err // 2) PUT the created/signed manifest // -func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, error) { +func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, string, error) { routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) if err != nil { - return nil, err + return nil, "", err } method := "GET" @@ -74,30 +76,30 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { - return nil, err + return nil, "", err } if err := auth.Authorize(req); err != nil { - return nil, err + return nil, "", err } res, _, err := r.doRequest(req) if err != nil { - return nil, err + return nil, "", err } defer res.Body.Close() if res.StatusCode != 200 { if res.StatusCode == 401 { - return nil, errLoginRequired + return nil, "", errLoginRequired } else if res.StatusCode == 404 { - return nil, ErrDoesNotExist + return nil, "", ErrDoesNotExist } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) + return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) } buf, err := ioutil.ReadAll(res.Body) if err != nil { - return nil, fmt.Errorf("Error while reading the http response: %s", err) + return nil, "", fmt.Errorf("Error while reading the http response: %s", err) } - return buf, nil + return buf, res.Header.Get(DockerDigestHeader), nil } // - Succeeded to head image blob (already exists) @@ -261,41 +263,41 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string } // Finally Push the (signed) manifest of the blobs we've just pushed -func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) error { +func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, manifestRdr io.Reader, auth *RequestAuthorization) (string, error) { routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName) if err != nil { - return err + return "", err } method := "PUT" log.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr) if err != nil { - return err + return "", err } if err := auth.Authorize(req); err != nil { - return err + return "", err } res, _, err := r.doRequest(req) if err != nil { - return err + return "", err } defer res.Body.Close() // All 2xx and 3xx responses can be accepted for a put. if res.StatusCode >= 400 { if res.StatusCode == 401 { - return errLoginRequired + return "", errLoginRequired } errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return err + return "", err } log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) + return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) } - return nil + return res.Header.Get(DockerDigestHeader), nil } type remoteTags struct { diff --git a/docs/v2/regexp.go b/docs/v2/regexp.go index e1e923b9..07484dcd 100644 --- a/docs/v2/regexp.go +++ b/docs/v2/regexp.go @@ -17,3 +17,6 @@ var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentReg // TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) + +// DigestRegexp matches valid digest types. +var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`) diff --git a/docs/v2/routes.go b/docs/v2/routes.go index 08f36e2f..de0a38fb 100644 --- a/docs/v2/routes.go +++ b/docs/v2/routes.go @@ -33,11 +33,11 @@ func Router() *mux.Router { Path("/v2/"). Name(RouteNameBase) - // GET /v2//manifest/ Image Manifest Fetch the image manifest identified by name and tag. - // PUT /v2//manifest/ Image Manifest Upload the image manifest identified by name and tag. - // DELETE /v2//manifest/ Image Manifest Delete the image identified by name and tag. + // GET /v2//manifest/ Image Manifest Fetch the image manifest identified by name and reference where reference can be a tag or digest. + // PUT /v2//manifest/ Image Manifest Upload the image manifest identified by name and reference where reference can be a tag or digest. + // DELETE /v2//manifest/ Image Manifest Delete the image identified by name and reference where reference can be a tag or digest. router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{tag:" + TagNameRegexp.String() + "}"). + Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}"). Name(RouteNameManifest) // GET /v2//tags/list Tags Fetch the tags under the repository identified by name. diff --git a/docs/v2/routes_test.go b/docs/v2/routes_test.go index 7682792e..0191feed 100644 --- a/docs/v2/routes_test.go +++ b/docs/v2/routes_test.go @@ -55,16 +55,16 @@ 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", }, }, { @@ -128,8 +128,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", }, }, { diff --git a/docs/v2/urls.go b/docs/v2/urls.go index d1380b47..38fa98af 100644 --- a/docs/v2/urls.go +++ b/docs/v2/urls.go @@ -74,11 +74,11 @@ 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. +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 }