github.com/sevki/docker@v1.7.1/registry/session_v2.go (about)

     1  package registry
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"strconv"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/distribution/digest"
    14  	"github.com/docker/distribution/registry/api/v2"
    15  	"github.com/docker/docker/pkg/httputils"
    16  )
    17  
    18  const DockerDigestHeader = "Docker-Content-Digest"
    19  
    20  func getV2Builder(e *Endpoint) *v2.URLBuilder {
    21  	if e.URLBuilder == nil {
    22  		e.URLBuilder = v2.NewURLBuilder(e.URL)
    23  	}
    24  	return e.URLBuilder
    25  }
    26  
    27  func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
    28  	// TODO check if should use Mirror
    29  	if index.Official {
    30  		ep, err = newEndpoint(REGISTRYSERVER, true, nil)
    31  		if err != nil {
    32  			return
    33  		}
    34  		err = validateEndpoint(ep)
    35  		if err != nil {
    36  			return
    37  		}
    38  	} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
    39  		ep = r.indexEndpoint
    40  	} else {
    41  		ep, err = NewEndpoint(index, nil)
    42  		if err != nil {
    43  			return
    44  		}
    45  	}
    46  
    47  	ep.URLBuilder = v2.NewURLBuilder(ep.URL)
    48  	return
    49  }
    50  
    51  // GetV2Authorization gets the authorization needed to the given image
    52  // If readonly access is requested, then the authorization may
    53  // only be used for Get operations.
    54  func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bool) (auth *RequestAuthorization, err error) {
    55  	scopes := []string{"pull"}
    56  	if !readOnly {
    57  		scopes = append(scopes, "push")
    58  	}
    59  
    60  	logrus.Debugf("Getting authorization for %s %s", imageName, scopes)
    61  	return NewRequestAuthorization(r.GetAuthConfig(true), ep, "repository", imageName, scopes), nil
    62  }
    63  
    64  //
    65  // 1) Check if TarSum of each layer exists /v2/
    66  //  1.a) if 200, continue
    67  //  1.b) if 300, then push the
    68  //  1.c) if anything else, err
    69  // 2) PUT the created/signed manifest
    70  //
    71  
    72  // GetV2ImageManifest simply fetches the bytes of a manifest and the remote
    73  // digest, if available in the request. Note that the application shouldn't
    74  // rely on the untrusted remoteDigest, and should also verify against a
    75  // locally provided digest, if applicable.
    76  func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) (remoteDigest digest.Digest, p []byte, err error) {
    77  	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
    78  	if err != nil {
    79  		return "", nil, err
    80  	}
    81  
    82  	method := "GET"
    83  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
    84  
    85  	req, err := http.NewRequest(method, routeURL, nil)
    86  	if err != nil {
    87  		return "", nil, err
    88  	}
    89  
    90  	if err := auth.Authorize(req); err != nil {
    91  		return "", nil, err
    92  	}
    93  
    94  	res, err := r.client.Do(req)
    95  	if err != nil {
    96  		return "", nil, err
    97  	}
    98  	defer res.Body.Close()
    99  
   100  	if res.StatusCode != 200 {
   101  		if res.StatusCode == 401 {
   102  			return "", nil, errLoginRequired
   103  		} else if res.StatusCode == 404 {
   104  			return "", nil, ErrDoesNotExist
   105  		}
   106  		return "", nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res)
   107  	}
   108  
   109  	p, err = ioutil.ReadAll(res.Body)
   110  	if err != nil {
   111  		return "", nil, fmt.Errorf("Error while reading the http response: %s", err)
   112  	}
   113  
   114  	dgstHdr := res.Header.Get(DockerDigestHeader)
   115  	if dgstHdr != "" {
   116  		remoteDigest, err = digest.ParseDigest(dgstHdr)
   117  		if err != nil {
   118  			// NOTE(stevvooe): Including the remote digest is optional. We
   119  			// don't need to verify against it, but it is good practice.
   120  			remoteDigest = ""
   121  			logrus.Debugf("error parsing remote digest when fetching %v: %v", routeURL, err)
   122  		}
   123  	}
   124  
   125  	return
   126  }
   127  
   128  // - Succeeded to head image blob (already exists)
   129  // - Failed with no error (continue to Push the Blob)
   130  // - Failed with error
   131  func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) {
   132  	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
   133  	if err != nil {
   134  		return false, err
   135  	}
   136  
   137  	method := "HEAD"
   138  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
   139  
   140  	req, err := http.NewRequest(method, routeURL, nil)
   141  	if err != nil {
   142  		return false, err
   143  	}
   144  	if err := auth.Authorize(req); err != nil {
   145  		return false, err
   146  	}
   147  	res, err := r.client.Do(req)
   148  	if err != nil {
   149  		return false, err
   150  	}
   151  	res.Body.Close() // close early, since we're not needing a body on this call .. yet?
   152  	switch {
   153  	case res.StatusCode >= 200 && res.StatusCode < 400:
   154  		// return something indicating no push needed
   155  		return true, nil
   156  	case res.StatusCode == 401:
   157  		return false, errLoginRequired
   158  	case res.StatusCode == 404:
   159  		// return something indicating blob push needed
   160  		return false, nil
   161  	}
   162  
   163  	return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res)
   164  }
   165  
   166  func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error {
   167  	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	method := "GET"
   173  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
   174  	req, err := http.NewRequest(method, routeURL, nil)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if err := auth.Authorize(req); err != nil {
   179  		return err
   180  	}
   181  	res, err := r.client.Do(req)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	defer res.Body.Close()
   186  	if res.StatusCode != 200 {
   187  		if res.StatusCode == 401 {
   188  			return errLoginRequired
   189  		}
   190  		return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res)
   191  	}
   192  
   193  	_, err = io.Copy(blobWrtr, res.Body)
   194  	return err
   195  }
   196  
   197  func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
   198  	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
   199  	if err != nil {
   200  		return nil, 0, err
   201  	}
   202  
   203  	method := "GET"
   204  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
   205  	req, err := http.NewRequest(method, routeURL, nil)
   206  	if err != nil {
   207  		return nil, 0, err
   208  	}
   209  	if err := auth.Authorize(req); err != nil {
   210  		return nil, 0, err
   211  	}
   212  	res, err := r.client.Do(req)
   213  	if err != nil {
   214  		return nil, 0, err
   215  	}
   216  	if res.StatusCode != 200 {
   217  		if res.StatusCode == 401 {
   218  			return nil, 0, errLoginRequired
   219  		}
   220  		return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res)
   221  	}
   222  	lenStr := res.Header.Get("Content-Length")
   223  	l, err := strconv.ParseInt(lenStr, 10, 64)
   224  	if err != nil {
   225  		return nil, 0, err
   226  	}
   227  
   228  	return res.Body, l, err
   229  }
   230  
   231  // Push the image to the server for storage.
   232  // 'layer' is an uncompressed reader of the blob to be pushed.
   233  // The server will generate it's own checksum calculation.
   234  func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error {
   235  	location, err := r.initiateBlobUpload(ep, imageName, auth)
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	method := "PUT"
   241  	logrus.Debugf("[registry] Calling %q %s", method, location)
   242  	req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
   243  	if err != nil {
   244  		return err
   245  	}
   246  	queryParams := req.URL.Query()
   247  	queryParams.Add("digest", dgst.String())
   248  	req.URL.RawQuery = queryParams.Encode()
   249  	if err := auth.Authorize(req); err != nil {
   250  		return err
   251  	}
   252  	res, err := r.client.Do(req)
   253  	if err != nil {
   254  		return err
   255  	}
   256  	defer res.Body.Close()
   257  
   258  	if res.StatusCode != 201 {
   259  		if res.StatusCode == 401 {
   260  			return errLoginRequired
   261  		}
   262  		errBody, err := ioutil.ReadAll(res.Body)
   263  		if err != nil {
   264  			return err
   265  		}
   266  		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
   267  		return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res)
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  // initiateBlobUpload gets the blob upload location for the given image name.
   274  func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *RequestAuthorization) (location string, err error) {
   275  	routeURL, err := getV2Builder(ep).BuildBlobUploadURL(imageName)
   276  	if err != nil {
   277  		return "", err
   278  	}
   279  
   280  	logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
   281  	req, err := http.NewRequest("POST", routeURL, nil)
   282  	if err != nil {
   283  		return "", err
   284  	}
   285  
   286  	if err := auth.Authorize(req); err != nil {
   287  		return "", err
   288  	}
   289  	res, err := r.client.Do(req)
   290  	if err != nil {
   291  		return "", err
   292  	}
   293  
   294  	if res.StatusCode != http.StatusAccepted {
   295  		if res.StatusCode == http.StatusUnauthorized {
   296  			return "", errLoginRequired
   297  		}
   298  		if res.StatusCode == http.StatusNotFound {
   299  			return "", ErrDoesNotExist
   300  		}
   301  
   302  		errBody, err := ioutil.ReadAll(res.Body)
   303  		if err != nil {
   304  			return "", err
   305  		}
   306  
   307  		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
   308  		return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res)
   309  	}
   310  
   311  	if location = res.Header.Get("Location"); location == "" {
   312  		return "", fmt.Errorf("registry did not return a Location header for resumable blob upload for image %s", imageName)
   313  	}
   314  
   315  	return
   316  }
   317  
   318  // Finally Push the (signed) manifest of the blobs we've just pushed
   319  func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, signedManifest, rawManifest []byte, auth *RequestAuthorization) (digest.Digest, error) {
   320  	routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
   321  	if err != nil {
   322  		return "", err
   323  	}
   324  
   325  	method := "PUT"
   326  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
   327  	req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
   328  	if err != nil {
   329  		return "", err
   330  	}
   331  	if err := auth.Authorize(req); err != nil {
   332  		return "", err
   333  	}
   334  	res, err := r.client.Do(req)
   335  	if err != nil {
   336  		return "", err
   337  	}
   338  	defer res.Body.Close()
   339  
   340  	// All 2xx and 3xx responses can be accepted for a put.
   341  	if res.StatusCode >= 400 {
   342  		if res.StatusCode == 401 {
   343  			return "", errLoginRequired
   344  		}
   345  		errBody, err := ioutil.ReadAll(res.Body)
   346  		if err != nil {
   347  			return "", err
   348  		}
   349  		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
   350  		return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
   351  	}
   352  
   353  	hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader))
   354  	if err != nil {
   355  		return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
   356  	}
   357  
   358  	dgstVerifier, err := digest.NewDigestVerifier(hdrDigest)
   359  	if err != nil {
   360  		return "", fmt.Errorf("invalid manifest digest from registry: %s", err)
   361  	}
   362  
   363  	dgstVerifier.Write(rawManifest)
   364  
   365  	if !dgstVerifier.Verified() {
   366  		computedDigest, _ := digest.FromBytes(rawManifest)
   367  		return "", fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", hdrDigest, computedDigest)
   368  	}
   369  
   370  	return hdrDigest, nil
   371  }
   372  
   373  type remoteTags struct {
   374  	Name string   `json:"name"`
   375  	Tags []string `json:"tags"`
   376  }
   377  
   378  // Given a repository name, returns a json array of string tags
   379  func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestAuthorization) ([]string, error) {
   380  	routeURL, err := getV2Builder(ep).BuildTagsURL(imageName)
   381  	if err != nil {
   382  		return nil, err
   383  	}
   384  
   385  	method := "GET"
   386  	logrus.Debugf("[registry] Calling %q %s", method, routeURL)
   387  
   388  	req, err := http.NewRequest(method, routeURL, nil)
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  	if err := auth.Authorize(req); err != nil {
   393  		return nil, err
   394  	}
   395  	res, err := r.client.Do(req)
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  	defer res.Body.Close()
   400  	if res.StatusCode != 200 {
   401  		if res.StatusCode == 401 {
   402  			return nil, errLoginRequired
   403  		} else if res.StatusCode == 404 {
   404  			return nil, ErrDoesNotExist
   405  		}
   406  		return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res)
   407  	}
   408  
   409  	var remote remoteTags
   410  	if err := json.NewDecoder(res.Body).Decode(&remote); err != nil {
   411  		return nil, fmt.Errorf("Error while decoding the http response: %s", err)
   412  	}
   413  	return remote.Tags, nil
   414  }