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>master
							parent
							
								
									1d6ccc1b72
								
							
						
					
					
						commit
						4b813b3847
					
				| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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-_+.=]+`)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,11 +33,11 @@ func Router() *mux.Router {
 | 
			
		|||
		Path("/v2/").
 | 
			
		||||
		Name(RouteNameBase)
 | 
			
		||||
 | 
			
		||||
	// GET      /v2/<name>/manifest/<tag>	Image Manifest	Fetch the image manifest identified by name and tag.
 | 
			
		||||
	// PUT      /v2/<name>/manifest/<tag>	Image Manifest	Upload the image manifest identified by name and tag.
 | 
			
		||||
	// DELETE   /v2/<name>/manifest/<tag>	Image Manifest	Delete the image 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/<reference>	Image Manifest	Upload the image manifest identified by name and reference where reference can be a tag or digest.
 | 
			
		||||
	// DELETE   /v2/<name>/manifest/<reference>	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/<name>/tags/list	Tags	Fetch the tags under the repository identified by name.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue