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 <agoldste@redhat.com>
This commit is contained in:
parent
1d6ccc1b72
commit
4b813b3847
5 changed files with 37 additions and 32 deletions
|
@ -12,6 +12,8 @@ import (
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const DockerDigestHeader = "Docker-Content-Digest"
|
||||||
|
|
||||||
func getV2Builder(e *Endpoint) *v2.URLBuilder {
|
func getV2Builder(e *Endpoint) *v2.URLBuilder {
|
||||||
if e.URLBuilder == nil {
|
if e.URLBuilder == nil {
|
||||||
e.URLBuilder = v2.NewURLBuilder(e.URL)
|
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
|
// 1.c) if anything else, err
|
||||||
// 2) PUT the created/signed manifest
|
// 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)
|
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
method := "GET"
|
method := "GET"
|
||||||
|
@ -74,30 +76,30 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
|
||||||
|
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
req, err := r.reqFactory.NewRequest(method, routeURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, _, err := r.doRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != 200 {
|
if res.StatusCode != 200 {
|
||||||
if res.StatusCode == 401 {
|
if res.StatusCode == 401 {
|
||||||
return nil, errLoginRequired
|
return nil, "", errLoginRequired
|
||||||
} else if res.StatusCode == 404 {
|
} 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)
|
buf, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
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)
|
// - 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
|
// 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)
|
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
method := "PUT"
|
method := "PUT"
|
||||||
log.Debugf("[registry] Calling %q %s", method, routeURL)
|
log.Debugf("[registry] Calling %q %s", method, routeURL)
|
||||||
req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr)
|
req, err := r.reqFactory.NewRequest(method, routeURL, manifestRdr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := auth.Authorize(req); err != nil {
|
if err := auth.Authorize(req); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
res, _, err := r.doRequest(req)
|
res, _, err := r.doRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
// All 2xx and 3xx responses can be accepted for a put.
|
// All 2xx and 3xx responses can be accepted for a put.
|
||||||
if res.StatusCode >= 400 {
|
if res.StatusCode >= 400 {
|
||||||
if res.StatusCode == 401 {
|
if res.StatusCode == 401 {
|
||||||
return errLoginRequired
|
return "", errLoginRequired
|
||||||
}
|
}
|
||||||
errBody, err := ioutil.ReadAll(res.Body)
|
errBody, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
|
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 {
|
type remoteTags struct {
|
||||||
|
|
|
@ -17,3 +17,6 @@ var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentReg
|
||||||
|
|
||||||
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
|
||||||
var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
|
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-_+.=]+`)
|
||||||
|
|
|
@ -33,11 +33,11 @@ func Router() *mux.Router {
|
||||||
Path("/v2/").
|
Path("/v2/").
|
||||||
Name(RouteNameBase)
|
Name(RouteNameBase)
|
||||||
|
|
||||||
// GET /v2/<name>/manifest/<tag> Image Manifest Fetch the image manifest identified by name and tag.
|
// GET /v2/<name>/manifest/<reference> Image Manifest Fetch the image manifest identified by name and reference where reference can be a tag or digest.
|
||||||
// PUT /v2/<name>/manifest/<tag> Image Manifest Upload the image manifest identified by name and tag.
|
// PUT /v2/<name>/manifest/<reference> Image Manifest Upload the image manifest identified by name and reference where reference can be a tag or digest.
|
||||||
// DELETE /v2/<name>/manifest/<tag> Image Manifest Delete the image identified by name and tag.
|
// DELETE /v2/<name>/manifest/<reference> Image Manifest Delete the image identified by name and reference where reference can be a tag or digest.
|
||||||
router.
|
router.
|
||||||
Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{tag:" + TagNameRegexp.String() + "}").
|
Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}").
|
||||||
Name(RouteNameManifest)
|
Name(RouteNameManifest)
|
||||||
|
|
||||||
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
|
// GET /v2/<name>/tags/list Tags Fetch the tags under the repository identified by name.
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestRouter(t *testing.T) {
|
||||||
RequestURI: "/v2/foo/manifests/bar",
|
RequestURI: "/v2/foo/manifests/bar",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
"tag": "bar",
|
"reference": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -64,7 +64,7 @@ func TestRouter(t *testing.T) {
|
||||||
RequestURI: "/v2/foo/bar/manifests/tag",
|
RequestURI: "/v2/foo/bar/manifests/tag",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar",
|
"name": "foo/bar",
|
||||||
"tag": "tag",
|
"reference": "tag",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -129,7 +129,7 @@ func TestRouter(t *testing.T) {
|
||||||
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
|
RequestURI: "/v2/foo/bar/manifests/manifests/tags",
|
||||||
Vars: map[string]string{
|
Vars: map[string]string{
|
||||||
"name": "foo/bar/manifests",
|
"name": "foo/bar/manifests",
|
||||||
"tag": "tags",
|
"reference": "tags",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,11 +74,11 @@ func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
||||||
return tagsURL.String(), nil
|
return tagsURL.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildManifestURL constructs a url for the manifest identified by name and tag.
|
// BuildManifestURL constructs a url for the manifest identified by name and reference.
|
||||||
func (ub *URLBuilder) BuildManifestURL(name, tag string) (string, error) {
|
func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
|
||||||
route := ub.cloneRoute(RouteNameManifest)
|
route := ub.cloneRoute(RouteNameManifest)
|
||||||
|
|
||||||
manifestURL, err := route.URL("name", name, "tag", tag)
|
manifestURL, err := route.URL("name", name, "reference", reference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue