2013-05-14 21:41:39 -04:00
package registry
import (
2013-05-14 23:27:15 -04:00
"bytes"
2013-05-14 21:41:39 -04:00
"encoding/json"
2013-05-15 16:22:57 -04:00
"errors"
2013-05-14 21:41:39 -04:00
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
2013-08-04 20:42:24 -04:00
"net"
2013-05-14 21:41:39 -04:00
"net/http"
2013-06-17 14:13:40 -04:00
"net/http/cookiejar"
2013-05-14 21:41:39 -04:00
"net/url"
2013-07-05 15:20:58 -04:00
"regexp"
2013-06-06 21:16:16 -04:00
"strconv"
2013-05-14 21:41:39 -04:00
"strings"
2013-08-04 20:42:24 -04:00
"time"
2013-05-14 21:41:39 -04:00
)
2013-07-22 17:50:32 -04:00
var (
ErrAlreadyExists = errors . New ( "Image already exists" )
ErrInvalidRepositoryName = errors . New ( "Invalid repository name (ex: \"registry.domain.tld/myrepos\")" )
2013-09-03 14:45:49 -04:00
ErrLoginRequired = errors . New ( "Authentication is required." )
2013-07-22 17:50:32 -04:00
)
2013-05-15 16:22:57 -04:00
2013-07-05 15:20:58 -04:00
func pingRegistryEndpoint ( endpoint string ) error {
2013-07-09 14:30:12 -04:00
if endpoint == auth . IndexServerAddress ( ) {
// Skip the check, we now this one is valid
// (and we never want to fallback to http in case of error)
return nil
}
2013-08-04 20:42:24 -04:00
httpDial := func ( proto string , addr string ) ( net . Conn , error ) {
// Set the connect timeout to 5 seconds
conn , err := net . DialTimeout ( proto , addr , time . Duration ( 5 ) * time . Second )
if err != nil {
return nil , err
}
// Set the recv timeout to 10 seconds
conn . SetDeadline ( time . Now ( ) . Add ( time . Duration ( 10 ) * time . Second ) )
return conn , nil
}
httpTransport := & http . Transport { Dial : httpDial }
client := & http . Client { Transport : httpTransport }
resp , err := client . Get ( endpoint + "_ping" )
2013-06-27 20:55:17 -04:00
if err != nil {
2013-07-05 15:20:58 -04:00
return err
}
2013-11-29 19:20:59 -05:00
defer resp . Body . Close ( )
2013-11-29 05:02:53 -05:00
2013-07-05 15:20:58 -04:00
if resp . Header . Get ( "X-Docker-Registry-Version" ) == "" {
return errors . New ( "This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)" )
}
return nil
}
2013-07-05 17:30:43 -04:00
func validateRepositoryName ( repositoryName string ) error {
var (
namespace string
name string
)
nameParts := strings . SplitN ( repositoryName , "/" , 2 )
if len ( nameParts ) < 2 {
namespace = "library"
name = nameParts [ 0 ]
} else {
namespace = nameParts [ 0 ]
name = nameParts [ 1 ]
}
2013-07-05 15:20:58 -04:00
validNamespace := regexp . MustCompile ( ` ^([a-z0-9_] { 4,30})$ ` )
if ! validNamespace . MatchString ( namespace ) {
return fmt . Errorf ( "Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30" , namespace )
}
2013-09-19 23:25:00 -04:00
validRepo := regexp . MustCompile ( ` ^([a-z0-9-_.]+)$ ` )
2013-07-05 15:20:58 -04:00
if ! validRepo . MatchString ( name ) {
2013-09-25 11:33:09 -04:00
return fmt . Errorf ( "Invalid repository name (%s), only [a-z0-9-_.] are allowed" , name )
2013-07-05 15:20:58 -04:00
}
return nil
}
// Resolves a repository name to a endpoint + name
func ResolveRepositoryName ( reposName string ) ( string , string , error ) {
2013-07-09 14:30:12 -04:00
if strings . Contains ( reposName , "://" ) {
// It cannot contain a scheme!
return "" , "" , ErrInvalidRepositoryName
}
2013-07-05 15:20:58 -04:00
nameParts := strings . SplitN ( reposName , "/" , 2 )
2013-08-04 20:59:12 -04:00
if ! strings . Contains ( nameParts [ 0 ] , "." ) && ! strings . Contains ( nameParts [ 0 ] , ":" ) &&
nameParts [ 0 ] != "localhost" {
2013-07-05 15:20:58 -04:00
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
2013-07-05 17:30:43 -04:00
err := validateRepositoryName ( reposName )
2013-07-09 14:30:12 -04:00
return auth . IndexServerAddress ( ) , reposName , err
2013-07-05 15:20:58 -04:00
}
if len ( nameParts ) < 2 {
// There is a dot in repos name (and no registry address)
// Is it a Registry address without repos name?
2013-07-09 14:30:12 -04:00
return "" , "" , ErrInvalidRepositoryName
2013-07-05 15:20:58 -04:00
}
hostname := nameParts [ 0 ]
2013-07-05 17:30:43 -04:00
reposName = nameParts [ 1 ]
2013-07-09 19:46:55 -04:00
if strings . Contains ( hostname , "index.docker.io" ) {
return "" , "" , fmt . Errorf ( "Invalid repository name, try \"%s\" instead" , reposName )
}
if err := validateRepositoryName ( reposName ) ; err != nil {
return "" , "" , err
}
2013-09-03 14:45:49 -04:00
endpoint , err := ExpandAndVerifyRegistryUrl ( hostname )
if err != nil {
return "" , "" , err
}
return endpoint , reposName , err
}
// this method expands the registry name as used in the prefix of a repo
// to a full url. if it already is a url, there will be no change.
// The registry is pinged to test if it http or https
func ExpandAndVerifyRegistryUrl ( hostname string ) ( string , error ) {
if strings . HasPrefix ( hostname , "http:" ) || strings . HasPrefix ( hostname , "https:" ) {
// if there is no slash after https:// (8 characters) then we have no path in the url
if strings . LastIndex ( hostname , "/" ) < 9 {
// there is no path given. Expand with default path
hostname = hostname + "/v1/"
}
if err := pingRegistryEndpoint ( hostname ) ; err != nil {
return "" , errors . New ( "Invalid Registry endpoint: " + err . Error ( ) )
}
return hostname , nil
}
2013-07-05 17:30:43 -04:00
endpoint := fmt . Sprintf ( "https://%s/v1/" , hostname )
2013-07-05 15:20:58 -04:00
if err := pingRegistryEndpoint ( endpoint ) ; err != nil {
utils . Debugf ( "Registry %s does not work (%s), falling back to http" , endpoint , err )
2013-07-05 17:30:43 -04:00
endpoint = fmt . Sprintf ( "http://%s/v1/" , hostname )
2013-07-05 15:20:58 -04:00
if err = pingRegistryEndpoint ( endpoint ) ; err != nil {
//TODO: triggering highland build can be done there without "failing"
2013-09-03 14:45:49 -04:00
return "" , errors . New ( "Invalid Registry endpoint: " + err . Error ( ) )
2013-07-05 15:20:58 -04:00
}
2013-06-27 20:55:17 -04:00
}
2013-09-03 14:45:49 -04:00
return endpoint , nil
2013-06-27 20:55:17 -04:00
}
2013-06-28 17:24:54 -04:00
func doWithCookies ( c * http . Client , req * http . Request ) ( * http . Response , error ) {
for _ , cookie := range c . Jar . Cookies ( req . URL ) {
req . AddCookie ( cookie )
2013-06-28 17:12:12 -04:00
}
2013-07-23 14:37:13 -04:00
res , err := c . Do ( req )
if err != nil {
return nil , err
}
if len ( res . Cookies ( ) ) > 0 {
c . Jar . SetCookies ( req . URL , res . Cookies ( ) )
}
return res , err
2013-06-28 17:24:54 -04:00
}
2013-06-28 17:12:12 -04:00
2013-11-04 15:49:34 -05:00
func setTokenAuth ( req * http . Request , token [ ] string ) {
2013-10-23 11:56:40 -04:00
if req . Header . Get ( "Authorization" ) == "" { // Don't override
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
}
}
2013-05-14 21:41:39 -04:00
// Retrieve the history of a given image from the Registry.
// Return a list of the parent's json (requested image included)
2013-07-05 15:37:07 -04:00
func ( r * Registry ) GetRemoteHistory ( imgID , registry string , token [ ] string ) ( [ ] string , error ) {
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/ancestry" , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-23 14:37:13 -04:00
res , err := doWithCookies ( r . client , req )
2013-10-08 15:21:32 -04:00
if err != nil {
2013-05-14 21:41:39 -04:00
return nil , err
}
defer res . Body . Close ( )
2013-10-08 15:21:32 -04:00
if res . StatusCode != 200 {
if res . StatusCode == 401 {
return nil , ErrLoginRequired
}
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to fetch remote history for %s" , res . StatusCode , imgID ) , res )
}
2013-05-14 21:41:39 -04:00
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-15 15:22:08 -04:00
return nil , fmt . Errorf ( "Error while reading the http response: %s" , err )
2013-05-14 21:41:39 -04:00
}
utils . Debugf ( "Ancestry: %s" , jsonString )
history := new ( [ ] string )
if err := json . Unmarshal ( jsonString , history ) ; err != nil {
return nil , err
}
return * history , nil
}
// Check if an image exists in the Registry
2013-07-02 18:27:22 -04:00
func ( r * Registry ) LookupRemoteImage ( imgID , registry string , token [ ] string ) bool {
2013-07-31 13:03:14 -04:00
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
return false
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-31 13:03:14 -04:00
res , err := doWithCookies ( r . client , req )
2013-06-03 15:20:52 -04:00
if err != nil {
return false
2013-06-03 15:14:57 -04:00
}
2013-06-03 15:20:52 -04:00
res . Body . Close ( )
2013-05-24 13:37:34 -04:00
return res . StatusCode == 200
2013-05-14 21:41:39 -04:00
}
// Retrieve an image from the Registry.
2013-07-02 18:27:22 -04:00
func ( r * Registry ) GetRemoteImageJSON ( imgID , registry string , token [ ] string ) ( [ ] byte , int , error ) {
2013-06-04 14:00:22 -04:00
// Get the JSON
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
2013-06-06 21:16:16 -04:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-14 21:41:39 -04:00
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-23 14:37:13 -04:00
res , err := doWithCookies ( r . client , req )
2013-05-14 21:41:39 -04:00
if err != nil {
2013-06-06 21:16:16 -04:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-14 21:41:39 -04:00
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-23 23:01:24 -04:00
return nil , - 1 , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d" , res . StatusCode ) , res )
2013-05-14 21:41:39 -04:00
}
2013-06-06 21:16:16 -04:00
imageSize , err := strconv . Atoi ( res . Header . Get ( "X-Docker-Size" ) )
if err != nil {
return nil , - 1 , err
}
2013-05-14 21:41:39 -04:00
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-06-06 21:16:16 -04:00
return nil , - 1 , fmt . Errorf ( "Failed to parse downloaded json: %s (%s)" , err , jsonString )
2013-05-14 21:41:39 -04:00
}
2013-06-06 21:16:16 -04:00
return jsonString , imageSize , nil
2013-05-14 21:41:39 -04:00
}
2013-07-05 15:37:07 -04:00
func ( r * Registry ) GetRemoteImageLayer ( imgID , registry string , token [ ] string ) ( io . ReadCloser , error ) {
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/layer" , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
2013-06-06 21:16:16 -04:00
return nil , fmt . Errorf ( "Error while getting from the server: %s\n" , err )
2013-05-14 21:41:39 -04:00
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-23 14:37:13 -04:00
res , err := doWithCookies ( r . client , req )
2013-05-14 21:41:39 -04:00
if err != nil {
2013-06-06 21:16:16 -04:00
return nil , err
2013-05-14 21:41:39 -04:00
}
2013-07-31 13:03:14 -04:00
if res . StatusCode != 200 {
2013-10-08 15:21:32 -04:00
res . Body . Close ( )
2013-07-31 13:03:14 -04:00
return nil , fmt . Errorf ( "Server error: Status %d while fetching image layer (%s)" ,
res . StatusCode , imgID )
}
2013-06-06 21:16:16 -04:00
return res . Body , nil
2013-05-14 21:41:39 -04:00
}
2013-05-15 14:50:52 -04:00
func ( r * Registry ) GetRemoteTags ( registries [ ] string , repository string , token [ ] string ) ( map [ string ] string , error ) {
2013-05-14 21:41:39 -04:00
if strings . Count ( repository , "/" ) == 0 {
// This will be removed once the Registry supports auto-resolution on
// the "library" namespace
repository = "library/" + repository
}
for _ , host := range registries {
2013-07-05 15:20:58 -04:00
endpoint := fmt . Sprintf ( "%srepositories/%s/tags" , host , repository )
2013-07-03 11:24:43 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , endpoint , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-23 14:37:13 -04:00
res , err := doWithCookies ( r . client , req )
2013-06-03 15:20:52 -04:00
if err != nil {
return nil , err
}
2013-05-29 14:24:50 -04:00
2013-06-19 14:07:36 -04:00
utils . Debugf ( "Got status code %d from %s" , res . StatusCode , endpoint )
2013-06-03 15:20:52 -04:00
defer res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 404 {
2013-05-14 21:41:39 -04:00
continue
} else if res . StatusCode == 404 {
return nil , fmt . Errorf ( "Repository not found" )
}
result := make ( map [ string ] string )
2013-06-04 14:00:22 -04:00
rawJSON , err := ioutil . ReadAll ( res . Body )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
2013-06-04 14:00:22 -04:00
if err := json . Unmarshal ( rawJSON , & result ) ; err != nil {
2013-05-14 21:41:39 -04:00
return nil , err
}
return result , nil
}
return nil , fmt . Errorf ( "Could not reach any registry endpoint" )
}
2013-10-22 14:49:13 -04:00
func ( r * Registry ) GetRepositoryData ( remote string ) ( * RepositoryData , error ) {
indexEp := r . indexEndpoint
2013-07-05 15:20:58 -04:00
repositoryTarget := fmt . Sprintf ( "%srepositories/%s/images" , indexEp , remote )
2013-05-14 21:41:39 -04:00
2013-07-22 17:50:32 -04:00
utils . Debugf ( "[registry] Calling GET %s" , repositoryTarget )
2013-07-03 11:24:43 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , repositoryTarget , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
if r . authConfig != nil && len ( r . authConfig . Username ) > 0 {
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
}
req . Header . Set ( "X-Docker-Token" , "true" )
2013-05-15 15:22:08 -04:00
res , err := r . client . Do ( req )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode == 401 {
2013-09-03 14:45:49 -04:00
return nil , ErrLoginRequired
2013-05-14 21:41:39 -04:00
}
// TODO: Right now we're ignoring checksums in the response body.
// In the future, we need to use them to check image validity.
if res . StatusCode != 200 {
2013-07-23 23:01:24 -04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code: %d" , res . StatusCode ) , res )
2013-05-14 21:41:39 -04:00
}
var tokens [ ] string
if res . Header . Get ( "X-Docker-Token" ) != "" {
tokens = res . Header [ "X-Docker-Token" ]
}
var endpoints [ ] string
2013-07-05 15:20:58 -04:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-14 21:41:39 -04:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 15:20:58 -04:00
// The Registry's URL scheme has to match the Index'
for _ , ep := range res . Header [ "X-Docker-Endpoints" ] {
endpoints = append ( endpoints , fmt . Sprintf ( "%s://%s/v1/" , urlScheme , ep ) )
}
2013-05-14 21:41:39 -04:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-06-04 14:00:22 -04:00
checksumsJSON , err := ioutil . ReadAll ( res . Body )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
remoteChecksums := [ ] * ImgData { }
2013-06-04 14:00:22 -04:00
if err := json . Unmarshal ( checksumsJSON , & remoteChecksums ) ; err != nil {
2013-05-14 21:41:39 -04:00
return nil , err
}
// Forge a better object from the retrieved data
imgsData := make ( map [ string ] * ImgData )
for _ , elem := range remoteChecksums {
2013-06-04 14:00:22 -04:00
imgsData [ elem . ID ] = elem
2013-05-14 21:41:39 -04:00
}
return & RepositoryData {
ImgList : imgsData ,
Endpoints : endpoints ,
Tokens : tokens ,
} , nil
}
2013-07-17 15:13:22 -04:00
func ( r * Registry ) PushImageChecksumRegistry ( imgData * ImgData , registry string , token [ ] string ) error {
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgData . ID + "/checksum" )
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/checksum" , nil )
2013-07-17 15:13:22 -04:00
if err != nil {
return err
}
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-07-17 15:13:22 -04:00
req . Header . Set ( "X-Docker-Checksum" , imgData . Checksum )
res , err := doWithCookies ( r . client , req )
if err != nil {
return fmt . Errorf ( "Failed to upload metadata: %s" , err )
}
defer res . Body . Close ( )
if len ( res . Cookies ( ) ) > 0 {
r . client . Jar . SetCookies ( req . URL , res . Cookies ( ) )
}
if res . StatusCode != 200 {
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
return fmt . Errorf ( "HTTP code %d while uploading metadata and error when trying to parse response body: %s" , res . StatusCode , err )
}
var jsonBody map [ string ] string
if err := json . Unmarshal ( errBody , & jsonBody ) ; err != nil {
errBody = [ ] byte ( err . Error ( ) )
} else if jsonBody [ "error" ] == "Image already exists" {
return ErrAlreadyExists
}
return fmt . Errorf ( "HTTP code %d while uploading metadata: %s" , res . StatusCode , errBody )
}
return nil
}
2013-05-14 23:27:15 -04:00
// Push a local image to the registry
2013-06-04 14:00:22 -04:00
func ( r * Registry ) PushImageJSONRegistry ( imgData * ImgData , jsonRaw [ ] byte , registry string , token [ ] string ) error {
2013-07-17 15:13:22 -04:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgData . ID + "/json" )
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/json" , bytes . NewReader ( jsonRaw ) )
2013-05-14 23:27:15 -04:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-05-15 14:30:40 -04:00
2013-05-15 15:22:08 -04:00
res , err := doWithCookies ( r . client , req )
2013-05-14 23:27:15 -04:00
if err != nil {
return fmt . Errorf ( "Failed to upload metadata: %s" , err )
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-08-09 19:52:05 -04:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d while uploading metadata and error when trying to parse response body: %s" , res . StatusCode , err ) , res )
2013-05-14 23:27:15 -04:00
}
var jsonBody map [ string ] string
if err := json . Unmarshal ( errBody , & jsonBody ) ; err != nil {
errBody = [ ] byte ( err . Error ( ) )
} else if jsonBody [ "error" ] == "Image already exists" {
2013-05-15 16:22:57 -04:00
return ErrAlreadyExists
2013-05-14 23:27:15 -04:00
}
2013-07-23 23:01:24 -04:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d while uploading metadata: %s" , res . StatusCode , errBody ) , res )
2013-05-14 23:27:15 -04:00
}
2013-05-15 14:30:40 -04:00
return nil
}
2013-05-14 23:27:15 -04:00
2013-07-22 17:50:32 -04:00
func ( r * Registry ) PushImageLayerRegistry ( imgID string , layer io . Reader , registry string , token [ ] string , jsonRaw [ ] byte ) ( checksum string , err error ) {
2013-07-17 15:13:22 -04:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgID + "/layer" )
tarsumLayer := & utils . TarSum { Reader : layer }
2013-07-22 17:50:32 -04:00
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgID + "/layer" , tarsumLayer )
2013-05-14 23:27:15 -04:00
if err != nil {
2013-07-17 15:13:22 -04:00
return "" , err
2013-05-14 23:27:15 -04:00
}
2013-05-15 14:30:40 -04:00
req . ContentLength = - 1
req . TransferEncoding = [ ] string { "chunked" }
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-05-15 15:22:08 -04:00
res , err := doWithCookies ( r . client , req )
2013-05-14 23:27:15 -04:00
if err != nil {
2013-07-17 15:13:22 -04:00
return "" , fmt . Errorf ( "Failed to upload layer: %s" , err )
2013-05-14 23:27:15 -04:00
}
2013-05-15 14:30:40 -04:00
defer res . Body . Close ( )
2013-05-14 23:27:15 -04:00
2013-05-15 14:30:40 -04:00
if res . StatusCode != 200 {
errBody , err := ioutil . ReadAll ( res . Body )
2013-05-14 23:27:15 -04:00
if err != nil {
2013-07-30 18:48:20 -04:00
return "" , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d while uploading metadata and error when trying to parse response body: %s" , res . StatusCode , err ) , res )
2013-05-14 23:27:15 -04:00
}
2013-07-30 18:48:20 -04:00
return "" , utils . NewHTTPRequestError ( fmt . Sprintf ( "Received HTTP code %d while uploading layer: %s" , res . StatusCode , errBody ) , res )
2013-05-14 23:27:15 -04:00
}
2013-07-22 17:50:32 -04:00
return tarsumLayer . Sum ( jsonRaw ) , nil
2013-05-14 23:27:15 -04:00
}
// push a tag on the registry.
// Remote has the format '<user>/<repo>
2013-05-15 14:30:40 -04:00
func ( r * Registry ) PushRegistryTag ( remote , revision , tag , registry string , token [ ] string ) error {
2013-05-14 23:27:15 -04:00
// "jsonify" the string
revision = "\"" + revision + "\""
2013-07-03 11:24:43 -04:00
path := fmt . Sprintf ( "repositories/%s/tags/%s" , remote , tag )
2013-05-14 23:27:15 -04:00
2013-07-03 11:24:43 -04:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + path , strings . NewReader ( revision ) )
2013-05-14 23:27:15 -04:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
2013-11-04 15:49:34 -05:00
setTokenAuth ( req , token )
2013-05-14 23:27:15 -04:00
req . ContentLength = int64 ( len ( revision ) )
2013-05-15 15:22:08 -04:00
res , err := doWithCookies ( r . client , req )
2013-05-14 23:27:15 -04:00
if err != nil {
return err
}
res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 201 {
2013-07-23 23:01:24 -04:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "Internal server error: %d trying to push tag %s on %s" , res . StatusCode , tag , remote ) , res )
2013-05-14 23:27:15 -04:00
}
return nil
}
2013-10-22 14:49:13 -04:00
func ( r * Registry ) PushImageJSONIndex ( remote string , imgList [ ] * ImgData , validate bool , regs [ ] string ) ( * RepositoryData , error ) {
2013-07-22 19:44:34 -04:00
cleanImgList := [ ] * ImgData { }
2013-10-22 14:49:13 -04:00
indexEp := r . indexEndpoint
2013-07-22 19:44:34 -04:00
if validate {
for _ , elem := range imgList {
if elem . Checksum != "" {
cleanImgList = append ( cleanImgList , elem )
}
}
} else {
cleanImgList = imgList
}
imgListJSON , err := json . Marshal ( cleanImgList )
2013-05-14 23:27:15 -04:00
if err != nil {
return nil , err
}
2013-05-16 17:33:29 -04:00
var suffix string
if validate {
suffix = "images"
}
2013-07-05 15:20:58 -04:00
u := fmt . Sprintf ( "%srepositories/%s/%s" , indexEp , remote , suffix )
2013-07-22 17:50:32 -04:00
utils . Debugf ( "[registry] PUT %s" , u )
2013-10-25 20:50:40 -04:00
utils . Debugf ( "Image list pushed to index:\n%s" , imgListJSON )
2013-07-03 11:24:43 -04:00
req , err := r . reqFactory . NewRequest ( "PUT" , u , bytes . NewReader ( imgListJSON ) )
2013-05-14 23:27:15 -04:00
if err != nil {
return nil , err
}
2013-10-22 14:56:48 -04:00
req . Header . Add ( "Content-type" , "application/json" )
2013-05-14 23:27:15 -04:00
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 14:00:22 -04:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-14 23:27:15 -04:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-10 14:21:56 -04:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-14 23:27:15 -04:00
2013-05-15 15:22:08 -04:00
res , err := r . client . Do ( req )
2013-05-14 23:27:15 -04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
// Redirect if necessary
for res . StatusCode >= 300 && res . StatusCode < 400 {
2013-10-25 20:50:40 -04:00
utils . Debugf ( "Redirected to %s" , res . Header . Get ( "Location" ) )
2013-07-03 11:24:43 -04:00
req , err = r . reqFactory . NewRequest ( "PUT" , res . Header . Get ( "Location" ) , bytes . NewReader ( imgListJSON ) )
2013-05-14 23:27:15 -04:00
if err != nil {
return nil , err
}
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 14:00:22 -04:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-14 23:27:15 -04:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-10 14:21:56 -04:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-15 15:22:08 -04:00
res , err = r . client . Do ( req )
2013-05-14 23:27:15 -04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
}
2013-05-16 17:33:29 -04:00
var tokens , endpoints [ ] string
2013-07-05 15:20:58 -04:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-16 17:33:29 -04:00
if ! validate {
if res . StatusCode != 200 && res . StatusCode != 201 {
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
return nil , err
}
2013-07-23 23:01:24 -04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Error: Status %d trying to push repository %s: %s" , res . StatusCode , remote , errBody ) , res )
2013-05-16 17:33:29 -04:00
}
if res . Header . Get ( "X-Docker-Token" ) != "" {
tokens = res . Header [ "X-Docker-Token" ]
utils . Debugf ( "Auth token: %v" , tokens )
} else {
return nil , fmt . Errorf ( "Index response didn't contain an access token" )
2013-05-14 23:27:15 -04:00
}
2013-05-16 17:33:29 -04:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 15:20:58 -04:00
// The Registry's URL scheme has to match the Index'
for _ , ep := range res . Header [ "X-Docker-Endpoints" ] {
endpoints = append ( endpoints , fmt . Sprintf ( "%s://%s/v1/" , urlScheme , ep ) )
}
2013-05-16 17:33:29 -04:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-05-14 23:27:15 -04:00
}
if validate {
if res . StatusCode != 204 {
2013-06-04 09:51:12 -04:00
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-14 23:27:15 -04:00
return nil , err
}
2013-07-23 23:01:24 -04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Error: Status %d trying to push checksums %s: %s" , res . StatusCode , remote , errBody ) , res )
2013-05-14 23:27:15 -04:00
}
}
return & RepositoryData {
Tokens : tokens ,
Endpoints : endpoints ,
} , nil
}
2013-05-14 21:41:39 -04:00
2013-05-15 14:50:52 -04:00
func ( r * Registry ) SearchRepositories ( term string ) ( * SearchResults , error ) {
2013-10-22 14:49:13 -04:00
utils . Debugf ( "Index server: %s" , r . indexEndpoint )
2013-07-05 15:20:58 -04:00
u := auth . IndexServerAddress ( ) + "search?q=" + url . QueryEscape ( term )
2013-08-02 03:08:08 -04:00
req , err := r . reqFactory . NewRequest ( "GET" , u , nil )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
2013-05-15 15:22:08 -04:00
res , err := r . client . Do ( req )
2013-05-14 21:41:39 -04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-23 23:01:24 -04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Unexepected status code %d" , res . StatusCode ) , res )
2013-05-14 21:41:39 -04:00
}
rawData , err := ioutil . ReadAll ( res . Body )
if err != nil {
return nil , err
}
result := new ( SearchResults )
err = json . Unmarshal ( rawData , result )
return result , err
}
2013-05-24 10:23:43 -04:00
func ( r * Registry ) GetAuthConfig ( withPasswd bool ) * auth . AuthConfig {
password := ""
if withPasswd {
password = r . authConfig . Password
}
2013-05-15 20:17:33 -04:00
return & auth . AuthConfig {
Username : r . authConfig . Username ,
2013-05-24 10:23:43 -04:00
Password : password ,
2013-05-15 20:31:11 -04:00
Email : r . authConfig . Email ,
2013-05-15 20:17:33 -04:00
}
}
2013-10-24 15:20:34 -04:00
type SearchResult struct {
StarCount int ` json:"star_count" `
IsOfficial bool ` json:"is_official" `
Name string ` json:"name" `
IsTrusted bool ` json:"is_trusted" `
Description string ` json:"description" `
}
2013-05-14 21:41:39 -04:00
type SearchResults struct {
2013-10-24 15:20:34 -04:00
Query string ` json:"query" `
NumResults int ` json:"num_results" `
Results [ ] SearchResult ` json:"results" `
2013-05-14 21:41:39 -04:00
}
type RepositoryData struct {
ImgList map [ string ] * ImgData
Endpoints [ ] string
Tokens [ ] string
}
type ImgData struct {
2013-06-04 14:00:22 -04:00
ID string ` json:"id" `
2013-05-14 21:41:39 -04:00
Checksum string ` json:"checksum,omitempty" `
Tag string ` json:",omitempty" `
}
type Registry struct {
2013-10-22 14:57:48 -04:00
client * http . Client
authConfig * auth . AuthConfig
reqFactory * utils . HTTPRequestFactory
2013-10-22 14:49:13 -04:00
indexEndpoint string
2013-06-28 19:29:02 -04:00
}
2013-10-22 14:49:13 -04:00
func NewRegistry ( authConfig * auth . AuthConfig , factory * utils . HTTPRequestFactory , indexEndpoint string ) ( r * Registry , err error ) {
2013-06-03 17:42:21 -04:00
httpTransport := & http . Transport {
DisableKeepAlives : true ,
2013-06-06 21:16:16 -04:00
Proxy : http . ProxyFromEnvironment ,
2013-06-03 17:42:21 -04:00
}
2013-06-17 14:13:40 -04:00
r = & Registry {
2013-05-14 21:41:39 -04:00
authConfig : authConfig ,
2013-06-03 17:42:21 -04:00
client : & http . Client {
Transport : httpTransport ,
} ,
2013-10-22 14:49:13 -04:00
indexEndpoint : indexEndpoint ,
2013-05-14 21:41:39 -04:00
}
2013-06-17 14:13:40 -04:00
r . client . Jar , err = cookiejar . New ( nil )
2013-06-28 17:12:12 -04:00
if err != nil {
return nil , err
}
2013-08-02 03:08:08 -04:00
2013-10-22 14:49:13 -04:00
// If we're working with a private registry over HTTPS, send Basic Auth headers
// alongside our requests.
if indexEndpoint != auth . IndexServerAddress ( ) && strings . HasPrefix ( indexEndpoint , "https://" ) {
utils . Debugf ( "Endpoint %s is eligible for private registry auth. Enabling decorator." , indexEndpoint )
dec := utils . NewHTTPAuthDecorator ( authConfig . Username , authConfig . Password )
factory . AddDecorator ( dec )
}
2013-08-02 03:08:08 -04:00
r . reqFactory = factory
2013-06-28 17:12:12 -04:00
return r , nil
2013-05-14 21:41:39 -04:00
}