2014-10-02 03:26:06 +02:00
package registry
import (
2015-03-18 07:45:30 +01:00
"bytes"
2014-10-02 03:26:06 +02:00
"encoding/json"
"fmt"
"io"
"io/ioutil"
2015-03-20 20:10:06 +01:00
"net/http"
2014-10-02 03:26:06 +02:00
"strconv"
2015-03-26 23:22:04 +01:00
"github.com/Sirupsen/logrus"
2015-03-18 07:45:30 +01:00
"github.com/docker/distribution/digest"
2015-04-01 00:02:27 +02:00
"github.com/docker/distribution/registry/api/v2"
2015-03-29 23:17:23 +02:00
"github.com/docker/docker/pkg/httputils"
2014-10-02 03:26:06 +02:00
)
2015-02-27 03:23:50 +01:00
const DockerDigestHeader = "Docker-Content-Digest"
2014-12-17 01:57:37 +01:00
func getV2Builder ( e * Endpoint ) * v2 . URLBuilder {
2014-12-19 23:44:18 +01:00
if e . URLBuilder == nil {
e . URLBuilder = v2 . NewURLBuilder ( e . URL )
}
return e . URLBuilder
2014-12-17 01:57:37 +01:00
}
2014-10-02 03:26:06 +02:00
2015-01-15 01:46:31 +01:00
func ( r * Session ) V2RegistryEndpoint ( index * IndexInfo ) ( ep * Endpoint , err error ) {
// TODO check if should use Mirror
if index . Official {
2015-05-16 03:35:04 +02:00
ep , err = newEndpoint ( REGISTRYSERVER , true , nil )
2014-12-23 22:40:06 +01:00
if err != nil {
return
}
2015-01-15 01:46:31 +01:00
err = validateEndpoint ( ep )
2014-12-19 23:44:18 +01:00
if err != nil {
return
}
2015-01-15 01:46:31 +01:00
} else if r . indexEndpoint . String ( ) == index . GetAuthConfigKey ( ) {
ep = r . indexEndpoint
2014-12-19 23:44:18 +01:00
} else {
2015-05-16 03:35:04 +02:00
ep , err = NewEndpoint ( index , nil )
2015-01-15 01:46:31 +01:00
if err != nil {
return
}
}
ep . URLBuilder = v2 . NewURLBuilder ( ep . URL )
return
}
// GetV2Authorization gets the authorization needed to the given image
2015-05-20 18:00:01 +02:00
// If readonly access is requested, then the authorization may
2015-01-15 01:46:31 +01:00
// only be used for Get operations.
func ( r * Session ) GetV2Authorization ( ep * Endpoint , imageName string , readOnly bool ) ( auth * RequestAuthorization , err error ) {
scopes := [ ] string { "pull" }
if ! readOnly {
scopes = append ( scopes , "push" )
2014-12-19 23:44:18 +01:00
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Getting authorization for %s %s" , imageName , scopes )
2015-01-15 01:46:31 +01:00
return NewRequestAuthorization ( r . GetAuthConfig ( true ) , ep , "repository" , imageName , scopes ) , nil
2014-10-02 03:26:06 +02:00
}
//
// 1) Check if TarSum of each layer exists /v2/
// 1.a) if 200, continue
// 1.b) if 300, then push the
// 1.c) if anything else, err
// 2) PUT the created/signed manifest
//
2015-05-29 07:50:56 +02:00
// GetV2ImageManifest simply fetches the bytes of a manifest and the remote
// digest, if available in the request. Note that the application shouldn't
// rely on the untrusted remoteDigest, and should also verify against a
// locally provided digest, if applicable.
func ( r * Session ) GetV2ImageManifest ( ep * Endpoint , imageName , tagName string , auth * RequestAuthorization ) ( remoteDigest digest . Digest , p [ ] byte , err error ) {
2015-01-15 01:46:31 +01:00
routeURL , err := getV2Builder ( ep ) . BuildManifestURL ( imageName , tagName )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-05-29 07:50:56 +02:00
return "" , nil , err
2014-10-02 03:26:06 +02:00
}
method := "GET"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2014-10-02 03:26:06 +02:00
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , nil )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-05-29 07:50:56 +02:00
return "" , nil , err
2014-10-02 03:26:06 +02:00
}
2015-05-29 07:50:56 +02:00
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
2015-05-29 07:50:56 +02:00
return "" , nil , err
2014-12-20 01:14:04 +01:00
}
2015-05-29 07:50:56 +02:00
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-05-29 07:50:56 +02:00
return "" , nil , err
2014-10-02 03:26:06 +02:00
}
defer res . Body . Close ( )
2015-05-29 07:50:56 +02:00
2014-10-02 03:26:06 +02:00
if res . StatusCode != 200 {
if res . StatusCode == 401 {
2015-05-29 07:50:56 +02:00
return "" , nil , errLoginRequired
2014-10-02 03:26:06 +02:00
} else if res . StatusCode == 404 {
2015-05-29 07:50:56 +02:00
return "" , nil , ErrDoesNotExist
2014-10-02 03:26:06 +02:00
}
2015-05-29 07:50:56 +02:00
return "" , nil , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to fetch for %s:%s" , res . StatusCode , imageName , tagName ) , res )
2014-10-02 03:26:06 +02:00
}
2015-05-29 07:50:56 +02:00
p , err = ioutil . ReadAll ( res . Body )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-05-29 07:50:56 +02:00
return "" , nil , fmt . Errorf ( "Error while reading the http response: %s" , err )
2014-10-02 03:26:06 +02:00
}
2015-03-18 07:45:30 +01:00
2015-05-29 07:50:56 +02:00
dgstHdr := res . Header . Get ( DockerDigestHeader )
if dgstHdr != "" {
remoteDigest , err = digest . ParseDigest ( dgstHdr )
if err != nil {
// NOTE(stevvooe): Including the remote digest is optional. We
// don't need to verify against it, but it is good practice.
remoteDigest = ""
logrus . Debugf ( "error parsing remote digest when fetching %v: %v" , routeURL , err )
}
}
return
2014-10-02 03:26:06 +02:00
}
2015-01-15 01:46:31 +01:00
// - Succeeded to head image blob (already exists)
// - Failed with no error (continue to Push the Blob)
2014-10-02 03:26:06 +02:00
// - Failed with error
2015-04-01 00:02:27 +02:00
func ( r * Session ) HeadV2ImageBlob ( ep * Endpoint , imageName string , dgst digest . Digest , auth * RequestAuthorization ) ( bool , error ) {
routeURL , err := getV2Builder ( ep ) . BuildBlobURL ( imageName , dgst )
2014-10-02 03:26:06 +02:00
if err != nil {
return false , err
}
2014-12-17 01:57:37 +01:00
method := "HEAD"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2014-10-02 03:26:06 +02:00
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , nil )
2014-10-02 03:26:06 +02:00
if err != nil {
return false , err
}
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
return false , err
2014-12-20 01:14:04 +01:00
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
return false , err
}
res . Body . Close ( ) // close early, since we're not needing a body on this call .. yet?
2015-01-28 03:09:53 +01:00
switch {
case res . StatusCode >= 200 && res . StatusCode < 400 :
2014-10-02 03:26:06 +02:00
// return something indicating no push needed
return true , nil
2015-01-31 01:11:47 +01:00
case res . StatusCode == 401 :
return false , errLoginRequired
2015-01-28 03:09:53 +01:00
case res . StatusCode == 404 :
2014-10-02 03:26:06 +02:00
// return something indicating blob push needed
return false , nil
}
2015-01-28 03:09:53 +01:00
2015-03-29 23:17:23 +02:00
return false , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying head request for %s - %s" , res . StatusCode , imageName , dgst ) , res )
2014-10-02 03:26:06 +02:00
}
2015-04-01 00:02:27 +02:00
func ( r * Session ) GetV2ImageBlob ( ep * Endpoint , imageName string , dgst digest . Digest , blobWrtr io . Writer , auth * RequestAuthorization ) error {
routeURL , err := getV2Builder ( ep ) . BuildBlobURL ( imageName , dgst )
2014-10-02 03:26:06 +02:00
if err != nil {
return err
}
method := "GET"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , nil )
2014-10-02 03:26:06 +02:00
if err != nil {
return err
}
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
return err
2014-12-20 01:14:04 +01:00
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
return err
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
if res . StatusCode == 401 {
return errLoginRequired
}
2015-03-29 23:17:23 +02:00
return httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to pull %s blob" , res . StatusCode , imageName ) , res )
2014-10-02 03:26:06 +02:00
}
_ , err = io . Copy ( blobWrtr , res . Body )
return err
}
2015-04-01 00:02:27 +02:00
func ( r * Session ) GetV2ImageBlobReader ( ep * Endpoint , imageName string , dgst digest . Digest , auth * RequestAuthorization ) ( io . ReadCloser , int64 , error ) {
routeURL , err := getV2Builder ( ep ) . BuildBlobURL ( imageName , dgst )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , 0 , err
}
method := "GET"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , nil )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , 0 , err
}
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
return nil , 0 , err
2014-12-20 01:14:04 +01:00
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , 0 , err
}
if res . StatusCode != 200 {
if res . StatusCode == 401 {
return nil , 0 , errLoginRequired
}
2015-03-29 23:17:23 +02:00
return nil , 0 , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to pull %s blob - %s" , res . StatusCode , imageName , dgst ) , res )
2014-10-02 03:26:06 +02:00
}
lenStr := res . Header . Get ( "Content-Length" )
l , err := strconv . ParseInt ( lenStr , 10 , 64 )
if err != nil {
return nil , 0 , err
}
return res . Body , l , err
}
// Push the image to the server for storage.
// 'layer' is an uncompressed reader of the blob to be pushed.
// The server will generate it's own checksum calculation.
2015-04-01 00:02:27 +02:00
func ( r * Session ) PutV2ImageBlob ( ep * Endpoint , imageName string , dgst digest . Digest , blobRdr io . Reader , auth * RequestAuthorization ) error {
2015-03-20 20:10:06 +01:00
location , err := r . initiateBlobUpload ( ep , imageName , auth )
2014-12-17 01:57:37 +01:00
if err != nil {
return err
}
2014-10-02 03:26:06 +02:00
method := "PUT"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , location )
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , location , ioutil . NopCloser ( blobRdr ) )
2014-10-02 03:26:06 +02:00
if err != nil {
2014-12-17 01:57:37 +01:00
return err
2014-10-02 03:26:06 +02:00
}
2015-01-02 20:13:11 +01:00
queryParams := req . URL . Query ( )
2015-04-01 00:02:27 +02:00
queryParams . Add ( "digest" , dgst . String ( ) )
2014-12-17 01:57:37 +01:00
req . URL . RawQuery = queryParams . Encode ( )
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
return err
2014-12-20 01:14:04 +01:00
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
2014-12-17 01:57:37 +01:00
return err
2014-10-02 03:26:06 +02:00
}
defer res . Body . Close ( )
2014-12-17 01:57:37 +01:00
2014-10-02 03:26:06 +02:00
if res . StatusCode != 201 {
if res . StatusCode == 401 {
2014-12-17 01:57:37 +01:00
return errLoginRequired
2014-10-02 03:26:06 +02:00
}
2015-01-26 23:00:51 +01:00
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
return err
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Unexpected response from server: %q %#v" , errBody , res . Header )
2015-03-29 23:17:23 +02:00
return httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to push %s blob - %s" , res . StatusCode , imageName , dgst ) , res )
2014-10-02 03:26:06 +02:00
}
2014-12-17 01:57:37 +01:00
return nil
2014-10-02 03:26:06 +02:00
}
2015-03-20 20:10:06 +01:00
// initiateBlobUpload gets the blob upload location for the given image name.
func ( r * Session ) initiateBlobUpload ( ep * Endpoint , imageName string , auth * RequestAuthorization ) ( location string , err error ) {
routeURL , err := getV2Builder ( ep ) . BuildBlobUploadURL ( imageName )
if err != nil {
return "" , err
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , "POST" , routeURL )
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( "POST" , routeURL , nil )
2015-03-20 20:10:06 +01:00
if err != nil {
return "" , err
}
if err := auth . Authorize ( req ) ; err != nil {
return "" , err
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2015-03-20 20:10:06 +01:00
if err != nil {
return "" , err
}
if res . StatusCode != http . StatusAccepted {
if res . StatusCode == http . StatusUnauthorized {
return "" , errLoginRequired
}
if res . StatusCode == http . StatusNotFound {
return "" , ErrDoesNotExist
}
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
return "" , err
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Unexpected response from server: %q %#v" , errBody , res . Header )
2015-03-29 23:17:23 +02:00
return "" , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: unexpected %d response status trying to initiate upload of %s" , res . StatusCode , imageName ) , res )
2015-03-20 20:10:06 +01:00
}
if location = res . Header . Get ( "Location" ) ; location == "" {
return "" , fmt . Errorf ( "registry did not return a Location header for resumable blob upload for image %s" , imageName )
}
return
}
2014-10-02 03:26:06 +02:00
// Finally Push the (signed) manifest of the blobs we've just pushed
2015-03-18 07:45:30 +01:00
func ( r * Session ) PutV2ImageManifest ( ep * Endpoint , imageName , tagName string , signedManifest , rawManifest [ ] byte , auth * RequestAuthorization ) ( digest . Digest , error ) {
2015-01-15 01:46:31 +01:00
routeURL , err := getV2Builder ( ep ) . BuildManifestURL ( imageName , tagName )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-02-27 03:23:50 +01:00
return "" , err
2014-10-02 03:26:06 +02:00
}
method := "PUT"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , bytes . NewReader ( signedManifest ) )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-02-27 03:23:50 +01:00
return "" , err
2014-10-02 03:26:06 +02:00
}
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
2015-02-27 03:23:50 +01:00
return "" , err
2014-12-20 01:14:04 +01:00
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
2015-02-27 03:23:50 +01:00
return "" , err
2014-10-02 03:26:06 +02:00
}
2015-01-26 23:00:51 +01:00
defer res . Body . Close ( )
2015-01-28 03:09:53 +01:00
// All 2xx and 3xx responses can be accepted for a put.
if res . StatusCode >= 400 {
2014-10-02 03:26:06 +02:00
if res . StatusCode == 401 {
2015-02-27 03:23:50 +01:00
return "" , errLoginRequired
2014-10-02 03:26:06 +02:00
}
2015-01-26 23:00:51 +01:00
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
2015-02-27 03:23:50 +01:00
return "" , err
2015-01-26 23:00:51 +01:00
}
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "Unexpected response from server: %q %#v" , errBody , res . Header )
2015-03-29 23:17:23 +02:00
return "" , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to push %s:%s manifest" , res . StatusCode , imageName , tagName ) , res )
2014-10-02 03:26:06 +02:00
}
2015-03-18 07:45:30 +01:00
hdrDigest , err := digest . ParseDigest ( res . Header . Get ( DockerDigestHeader ) )
if err != nil {
return "" , fmt . Errorf ( "invalid manifest digest from registry: %s" , err )
}
dgstVerifier , err := digest . NewDigestVerifier ( hdrDigest )
if err != nil {
return "" , fmt . Errorf ( "invalid manifest digest from registry: %s" , err )
}
dgstVerifier . Write ( rawManifest )
if ! dgstVerifier . Verified ( ) {
computedDigest , _ := digest . FromBytes ( rawManifest )
return "" , fmt . Errorf ( "unable to verify manifest digest: registry has %q, computed %q" , hdrDigest , computedDigest )
}
return hdrDigest , nil
2014-10-02 03:26:06 +02:00
}
2015-01-08 00:55:29 +01:00
type remoteTags struct {
2015-03-23 22:23:47 +01:00
Name string ` json:"name" `
Tags [ ] string ` json:"tags" `
2015-01-08 00:55:29 +01:00
}
2014-10-02 03:26:06 +02:00
// Given a repository name, returns a json array of string tags
2015-01-15 01:46:31 +01:00
func ( r * Session ) GetV2RemoteTags ( ep * Endpoint , imageName string , auth * RequestAuthorization ) ( [ ] string , error ) {
routeURL , err := getV2Builder ( ep ) . BuildTagsURL ( imageName )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , err
}
method := "GET"
2015-03-26 23:22:04 +01:00
logrus . Debugf ( "[registry] Calling %q %s" , method , routeURL )
2014-10-02 03:26:06 +02:00
2015-05-14 16:12:54 +02:00
req , err := http . NewRequest ( method , routeURL , nil )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , err
}
2014-12-22 23:58:08 +01:00
if err := auth . Authorize ( req ) ; err != nil {
2014-12-20 01:14:04 +01:00
return nil , err
}
2015-05-14 16:12:54 +02:00
res , err := r . client . Do ( req )
2014-10-02 03:26:06 +02:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
if res . StatusCode == 401 {
return nil , errLoginRequired
} else if res . StatusCode == 404 {
return nil , ErrDoesNotExist
}
2015-03-29 23:17:23 +02:00
return nil , httputils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to fetch for %s" , res . StatusCode , imageName ) , res )
2014-10-02 03:26:06 +02:00
}
2015-01-08 00:55:29 +01:00
var remote remoteTags
2015-04-26 18:50:25 +02:00
if err := json . NewDecoder ( res . Body ) . Decode ( & remote ) ; err != nil {
2014-10-02 03:26:06 +02:00
return nil , fmt . Errorf ( "Error while decoding the http response: %s" , err )
}
2015-03-11 20:45:01 +01:00
return remote . Tags , nil
2014-10-02 03:26:06 +02:00
}