2013-05-15 01:41:39 +00:00
package registry
import (
2013-05-15 03:27:15 +00:00
"bytes"
2014-02-24 02:33:46 +00:00
"crypto/sha256"
2013-05-15 01:41:39 +00:00
"encoding/json"
2013-05-15 20:22:57 +00:00
"errors"
2013-05-15 01:41:39 +00:00
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
2013-08-05 00:42:24 +00:00
"net"
2013-05-15 01:41:39 +00:00
"net/http"
2013-06-17 18:13:40 +00:00
"net/http/cookiejar"
2013-05-15 01:41:39 +00:00
"net/url"
2013-07-05 19:20:58 +00:00
"regexp"
2013-06-07 01:16:16 +00:00
"strconv"
2013-05-15 01:41:39 +00:00
"strings"
2013-08-05 00:42:24 +00:00
"time"
2013-05-15 01:41:39 +00:00
)
2013-07-22 21:50:32 +00:00
var (
ErrAlreadyExists = errors . New ( "Image already exists" )
ErrInvalidRepositoryName = errors . New ( "Invalid repository name (ex: \"registry.domain.tld/myrepos\")" )
2014-02-03 19:38:34 +00:00
errLoginRequired = errors . New ( "Authentication is required." )
2013-07-22 21:50:32 +00:00
)
2013-05-15 20:22:57 +00:00
2014-01-02 16:51:42 +00:00
func pingRegistryEndpoint ( endpoint string ) ( bool , error ) {
2013-07-09 18:30:12 +00: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)
2014-01-02 16:51:42 +00:00
return false , nil
2013-07-09 18:30:12 +00:00
}
2013-08-05 00:42:24 +00: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-28 00:55:17 +00:00
if err != nil {
2014-01-02 16:51:42 +00:00
return false , err
2013-07-05 19:20:58 +00:00
}
2013-11-30 00:20:59 +00:00
defer resp . Body . Close ( )
2013-11-29 10:02:53 +00:00
2013-07-05 19:20:58 +00:00
if resp . Header . Get ( "X-Docker-Registry-Version" ) == "" {
2014-01-02 16:51:42 +00:00
return false , errors . New ( "This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)" )
2013-07-05 19:20:58 +00:00
}
2014-01-02 16:51:42 +00:00
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
utils . Debugf ( "Registry standalone header: '%s'" , standalone )
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry
if standalone == "" {
return true , nil
2014-01-13 22:55:31 +00:00
// Accepted values are "true" (case-insensitive) and "1".
2014-01-02 16:51:42 +00:00
} else if strings . EqualFold ( standalone , "true" ) || standalone == "1" {
return true , nil
}
// Otherwise, not standalone
return false , nil
2013-07-05 19:20:58 +00:00
}
2013-07-05 21:30:43 +00: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 19:20:58 +00: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-20 03:25:00 +00:00
validRepo := regexp . MustCompile ( ` ^([a-z0-9-_.]+)$ ` )
2013-07-05 19:20:58 +00:00
if ! validRepo . MatchString ( name ) {
2013-09-25 15:33:09 +00:00
return fmt . Errorf ( "Invalid repository name (%s), only [a-z0-9-_.] are allowed" , name )
2013-07-05 19:20:58 +00:00
}
return nil
}
// Resolves a repository name to a endpoint + name
func ResolveRepositoryName ( reposName string ) ( string , string , error ) {
2013-07-09 18:30:12 +00:00
if strings . Contains ( reposName , "://" ) {
// It cannot contain a scheme!
return "" , "" , ErrInvalidRepositoryName
}
2013-07-05 19:20:58 +00:00
nameParts := strings . SplitN ( reposName , "/" , 2 )
2013-08-05 00:59:12 +00:00
if ! strings . Contains ( nameParts [ 0 ] , "." ) && ! strings . Contains ( nameParts [ 0 ] , ":" ) &&
nameParts [ 0 ] != "localhost" {
2013-07-05 19:20:58 +00:00
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
2013-07-05 21:30:43 +00:00
err := validateRepositoryName ( reposName )
2013-07-09 18:30:12 +00:00
return auth . IndexServerAddress ( ) , reposName , err
2013-07-05 19:20:58 +00: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 18:30:12 +00:00
return "" , "" , ErrInvalidRepositoryName
2013-07-05 19:20:58 +00:00
}
hostname := nameParts [ 0 ]
2013-07-05 21:30:43 +00:00
reposName = nameParts [ 1 ]
2013-07-09 23:46:55 +00: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 18:45:49 +00: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/"
}
2014-01-02 16:51:42 +00:00
if _ , err := pingRegistryEndpoint ( hostname ) ; err != nil {
2013-09-03 18:45:49 +00:00
return "" , errors . New ( "Invalid Registry endpoint: " + err . Error ( ) )
}
return hostname , nil
}
2013-07-05 21:30:43 +00:00
endpoint := fmt . Sprintf ( "https://%s/v1/" , hostname )
2014-01-02 16:51:42 +00:00
if _ , err := pingRegistryEndpoint ( endpoint ) ; err != nil {
2013-07-05 19:20:58 +00:00
utils . Debugf ( "Registry %s does not work (%s), falling back to http" , endpoint , err )
2013-07-05 21:30:43 +00:00
endpoint = fmt . Sprintf ( "http://%s/v1/" , hostname )
2014-01-02 16:51:42 +00:00
if _ , err = pingRegistryEndpoint ( endpoint ) ; err != nil {
2013-07-05 19:20:58 +00:00
//TODO: triggering highland build can be done there without "failing"
2013-09-03 18:45:49 +00:00
return "" , errors . New ( "Invalid Registry endpoint: " + err . Error ( ) )
2013-07-05 19:20:58 +00:00
}
2013-06-28 00:55:17 +00:00
}
2013-09-03 18:45:49 +00:00
return endpoint , nil
2013-06-28 00:55:17 +00:00
}
2013-06-28 21:24:54 +00: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 21:12:12 +00:00
}
2013-07-23 18:37:13 +00: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 21:24:54 +00:00
}
2013-06-28 21:12:12 +00:00
2013-11-04 20:49:34 +00:00
func setTokenAuth ( req * http . Request , token [ ] string ) {
2013-10-23 15:56:40 +00:00
if req . Header . Get ( "Authorization" ) == "" { // Don't override
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
}
}
2013-05-15 01:41:39 +00: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 19:37:07 +00:00
func ( r * Registry ) GetRemoteHistory ( imgID , registry string , token [ ] string ) ( [ ] string , error ) {
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/ancestry" , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-23 18:37:13 +00:00
res , err := doWithCookies ( r . client , req )
2013-10-08 19:21:32 +00:00
if err != nil {
2013-05-15 01:41:39 +00:00
return nil , err
}
defer res . Body . Close ( )
2013-10-08 19:21:32 +00:00
if res . StatusCode != 200 {
if res . StatusCode == 401 {
2014-02-03 19:38:34 +00:00
return nil , errLoginRequired
2013-10-08 19:21:32 +00:00
}
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Server error: %d trying to fetch remote history for %s" , res . StatusCode , imgID ) , res )
}
2013-05-15 01:41:39 +00:00
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-15 19:22:08 +00:00
return nil , fmt . Errorf ( "Error while reading the http response: %s" , err )
2013-05-15 01:41:39 +00: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
2014-01-20 21:39:35 +00:00
// TODO: This method should return the errors instead of masking them and returning false
2013-07-02 22:27:22 +00:00
func ( r * Registry ) LookupRemoteImage ( imgID , registry string , token [ ] string ) bool {
2013-07-31 17:03:14 +00:00
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
2014-01-20 21:39:35 +00:00
utils . Errorf ( "Error in LookupRemoteImage %s" , err )
2013-05-15 01:41:39 +00:00
return false
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-31 17:03:14 +00:00
res , err := doWithCookies ( r . client , req )
2013-06-03 19:20:52 +00:00
if err != nil {
2014-01-20 21:39:35 +00:00
utils . Errorf ( "Error in LookupRemoteImage %s" , err )
2013-06-03 19:20:52 +00:00
return false
2013-06-03 19:14:57 +00:00
}
2013-06-03 19:20:52 +00:00
res . Body . Close ( )
2013-05-24 17:37:34 +00:00
return res . StatusCode == 200
2013-05-15 01:41:39 +00:00
}
// Retrieve an image from the Registry.
2013-07-02 22:27:22 +00:00
func ( r * Registry ) GetRemoteImageJSON ( imgID , registry string , token [ ] string ) ( [ ] byte , int , error ) {
2013-06-04 18:00:22 +00:00
// Get the JSON
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
2013-06-07 01:16:16 +00:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-15 01:41:39 +00:00
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-23 18:37:13 +00:00
res , err := doWithCookies ( r . client , req )
2013-05-15 01:41:39 +00:00
if err != nil {
2013-06-07 01:16:16 +00:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-15 01:41:39 +00:00
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-24 03:01:24 +00:00
return nil , - 1 , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d" , res . StatusCode ) , res )
2013-05-15 01:41:39 +00:00
}
2013-06-07 01:16:16 +00:00
imageSize , err := strconv . Atoi ( res . Header . Get ( "X-Docker-Size" ) )
if err != nil {
return nil , - 1 , err
}
2013-05-15 01:41:39 +00:00
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-06-07 01:16:16 +00:00
return nil , - 1 , fmt . Errorf ( "Failed to parse downloaded json: %s (%s)" , err , jsonString )
2013-05-15 01:41:39 +00:00
}
2013-06-07 01:16:16 +00:00
return jsonString , imageSize , nil
2013-05-15 01:41:39 +00:00
}
2013-07-05 19:37:07 +00:00
func ( r * Registry ) GetRemoteImageLayer ( imgID , registry string , token [ ] string ) ( io . ReadCloser , error ) {
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , registry + "images/" + imgID + "/layer" , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
2013-06-07 01:16:16 +00:00
return nil , fmt . Errorf ( "Error while getting from the server: %s\n" , err )
2013-05-15 01:41:39 +00:00
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-23 18:37:13 +00:00
res , err := doWithCookies ( r . client , req )
2013-05-15 01:41:39 +00:00
if err != nil {
2013-06-07 01:16:16 +00:00
return nil , err
2013-05-15 01:41:39 +00:00
}
2013-07-31 17:03:14 +00:00
if res . StatusCode != 200 {
2013-10-08 19:21:32 +00:00
res . Body . Close ( )
2013-07-31 17:03:14 +00:00
return nil , fmt . Errorf ( "Server error: Status %d while fetching image layer (%s)" ,
res . StatusCode , imgID )
}
2013-06-07 01:16:16 +00:00
return res . Body , nil
2013-05-15 01:41:39 +00:00
}
2013-05-15 18:50:52 +00:00
func ( r * Registry ) GetRemoteTags ( registries [ ] string , repository string , token [ ] string ) ( map [ string ] string , error ) {
2013-05-15 01:41:39 +00: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 19:20:58 +00:00
endpoint := fmt . Sprintf ( "%srepositories/%s/tags" , host , repository )
2013-07-03 15:24:43 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , endpoint , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-23 18:37:13 +00:00
res , err := doWithCookies ( r . client , req )
2013-06-03 19:20:52 +00:00
if err != nil {
return nil , err
}
2013-05-29 18:24:50 +00:00
2013-06-19 18:07:36 +00:00
utils . Debugf ( "Got status code %d from %s" , res . StatusCode , endpoint )
2013-06-03 19:20:52 +00:00
defer res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 404 {
2013-05-15 01:41:39 +00:00
continue
} else if res . StatusCode == 404 {
return nil , fmt . Errorf ( "Repository not found" )
}
result := make ( map [ string ] string )
2013-06-04 18:00:22 +00:00
rawJSON , err := ioutil . ReadAll ( res . Body )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
2013-06-04 18:00:22 +00:00
if err := json . Unmarshal ( rawJSON , & result ) ; err != nil {
2013-05-15 01:41:39 +00:00
return nil , err
}
return result , nil
}
return nil , fmt . Errorf ( "Could not reach any registry endpoint" )
}
2013-10-22 18:49:13 +00:00
func ( r * Registry ) GetRepositoryData ( remote string ) ( * RepositoryData , error ) {
indexEp := r . indexEndpoint
2013-07-05 19:20:58 +00:00
repositoryTarget := fmt . Sprintf ( "%srepositories/%s/images" , indexEp , remote )
2013-05-15 01:41:39 +00:00
2013-07-22 21:50:32 +00:00
utils . Debugf ( "[registry] Calling GET %s" , repositoryTarget )
2013-07-03 15:24:43 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , repositoryTarget , nil )
2013-05-15 01:41:39 +00: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 19:22:08 +00:00
res , err := r . client . Do ( req )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode == 401 {
2014-02-03 19:38:34 +00:00
return nil , errLoginRequired
2013-05-15 01:41:39 +00: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-24 03:01:24 +00:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code: %d" , res . StatusCode ) , res )
2013-05-15 01:41:39 +00:00
}
var tokens [ ] string
if res . Header . Get ( "X-Docker-Token" ) != "" {
tokens = res . Header [ "X-Docker-Token" ]
}
var endpoints [ ] string
2013-07-05 19:20:58 +00:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-15 01:41:39 +00:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 19:20:58 +00: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-15 01:41:39 +00:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-06-04 18:00:22 +00:00
checksumsJSON , err := ioutil . ReadAll ( res . Body )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
remoteChecksums := [ ] * ImgData { }
2013-06-04 18:00:22 +00:00
if err := json . Unmarshal ( checksumsJSON , & remoteChecksums ) ; err != nil {
2013-05-15 01:41:39 +00:00
return nil , err
}
// Forge a better object from the retrieved data
imgsData := make ( map [ string ] * ImgData )
for _ , elem := range remoteChecksums {
2013-06-04 18:00:22 +00:00
imgsData [ elem . ID ] = elem
2013-05-15 01:41:39 +00:00
}
return & RepositoryData {
ImgList : imgsData ,
Endpoints : endpoints ,
Tokens : tokens ,
} , nil
}
2013-07-17 19:13:22 +00: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 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/checksum" , nil )
2013-07-17 19:13:22 +00:00
if err != nil {
return err
}
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-07-17 19:13:22 +00:00
req . Header . Set ( "X-Docker-Checksum" , imgData . Checksum )
2014-02-24 20:40:33 +00:00
req . Header . Set ( "X-Docker-Checksum-Payload" , imgData . checksumPayload )
2013-07-17 19:13:22 +00:00
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-15 03:27:15 +00:00
// Push a local image to the registry
2013-06-04 18:00:22 +00:00
func ( r * Registry ) PushImageJSONRegistry ( imgData * ImgData , jsonRaw [ ] byte , registry string , token [ ] string ) error {
2013-07-17 19:13:22 +00:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgData . ID + "/json" )
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/json" , bytes . NewReader ( jsonRaw ) )
2013-05-15 03:27:15 +00:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-05-15 18:30:40 +00:00
2013-05-15 19:22:08 +00:00
res , err := doWithCookies ( r . client , req )
2013-05-15 03:27:15 +00: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 23:52:05 +00: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-15 03:27:15 +00: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 20:22:57 +00:00
return ErrAlreadyExists
2013-05-15 03:27:15 +00:00
}
2013-07-24 03:01:24 +00:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d while uploading metadata: %s" , res . StatusCode , errBody ) , res )
2013-05-15 03:27:15 +00:00
}
2013-05-15 18:30:40 +00:00
return nil
}
2013-05-15 03:27:15 +00:00
2014-02-24 02:33:46 +00:00
func ( r * Registry ) PushImageLayerRegistry ( imgID string , layer io . Reader , registry string , token [ ] string , jsonRaw [ ] byte ) ( checksum string , checksumPayload string , err error ) {
2013-07-17 19:13:22 +00:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgID + "/layer" )
2014-02-24 02:33:46 +00:00
h := sha256 . New ( )
checksumLayer := & utils . CheckSum { Reader : layer , Hash : h }
tarsumLayer := & utils . TarSum { Reader : checksumLayer }
2013-07-22 21:50:32 +00:00
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgID + "/layer" , tarsumLayer )
2013-05-15 03:27:15 +00:00
if err != nil {
2014-02-24 02:33:46 +00:00
return "" , "" , err
2013-05-15 03:27:15 +00:00
}
2013-05-15 18:30:40 +00:00
req . ContentLength = - 1
req . TransferEncoding = [ ] string { "chunked" }
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-05-15 19:22:08 +00:00
res , err := doWithCookies ( r . client , req )
2013-05-15 03:27:15 +00:00
if err != nil {
2014-02-24 02:33:46 +00:00
return "" , "" , fmt . Errorf ( "Failed to upload layer: %s" , err )
2013-05-15 03:27:15 +00:00
}
2013-12-19 20:32:58 +00:00
if rc , ok := layer . ( io . Closer ) ; ok {
if err := rc . Close ( ) ; err != nil {
2014-02-24 02:33:46 +00:00
return "" , "" , err
2013-12-19 20:32:58 +00:00
}
}
2013-05-15 18:30:40 +00:00
defer res . Body . Close ( )
2013-05-15 03:27:15 +00:00
2013-05-15 18:30:40 +00:00
if res . StatusCode != 200 {
errBody , err := ioutil . ReadAll ( res . Body )
2013-05-15 03:27:15 +00:00
if err != nil {
2014-02-24 02:33:46 +00: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-15 03:27:15 +00:00
}
2014-02-24 02:33:46 +00:00
return "" , "" , utils . NewHTTPRequestError ( fmt . Sprintf ( "Received HTTP code %d while uploading layer: %s" , res . StatusCode , errBody ) , res )
2013-05-15 03:27:15 +00:00
}
2014-02-24 02:33:46 +00:00
checksumPayload = "sha256:" + checksumLayer . Sum ( )
return tarsumLayer . Sum ( jsonRaw ) , checksumPayload , nil
2013-05-15 03:27:15 +00:00
}
// push a tag on the registry.
// Remote has the format '<user>/<repo>
2013-05-15 18:30:40 +00:00
func ( r * Registry ) PushRegistryTag ( remote , revision , tag , registry string , token [ ] string ) error {
2013-05-15 03:27:15 +00:00
// "jsonify" the string
revision = "\"" + revision + "\""
2013-07-03 15:24:43 +00:00
path := fmt . Sprintf ( "repositories/%s/tags/%s" , remote , tag )
2013-05-15 03:27:15 +00:00
2013-07-03 15:24:43 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + path , strings . NewReader ( revision ) )
2013-05-15 03:27:15 +00:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
2013-11-04 20:49:34 +00:00
setTokenAuth ( req , token )
2013-05-15 03:27:15 +00:00
req . ContentLength = int64 ( len ( revision ) )
2013-05-15 19:22:08 +00:00
res , err := doWithCookies ( r . client , req )
2013-05-15 03:27:15 +00:00
if err != nil {
return err
}
res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 201 {
2013-07-24 03:01:24 +00:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "Internal server error: %d trying to push tag %s on %s" , res . StatusCode , tag , remote ) , res )
2013-05-15 03:27:15 +00:00
}
return nil
}
2013-10-22 18:49:13 +00:00
func ( r * Registry ) PushImageJSONIndex ( remote string , imgList [ ] * ImgData , validate bool , regs [ ] string ) ( * RepositoryData , error ) {
2013-07-22 23:44:34 +00:00
cleanImgList := [ ] * ImgData { }
2013-10-22 18:49:13 +00:00
indexEp := r . indexEndpoint
2013-07-22 23:44:34 +00:00
if validate {
for _ , elem := range imgList {
if elem . Checksum != "" {
cleanImgList = append ( cleanImgList , elem )
}
}
} else {
cleanImgList = imgList
}
imgListJSON , err := json . Marshal ( cleanImgList )
2013-05-15 03:27:15 +00:00
if err != nil {
return nil , err
}
2013-05-16 21:33:29 +00:00
var suffix string
if validate {
suffix = "images"
}
2013-07-05 19:20:58 +00:00
u := fmt . Sprintf ( "%srepositories/%s/%s" , indexEp , remote , suffix )
2013-07-22 21:50:32 +00:00
utils . Debugf ( "[registry] PUT %s" , u )
2013-10-26 00:50:40 +00:00
utils . Debugf ( "Image list pushed to index:\n%s" , imgListJSON )
2013-07-03 15:24:43 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , u , bytes . NewReader ( imgListJSON ) )
2013-05-15 03:27:15 +00:00
if err != nil {
return nil , err
}
2013-10-22 18:56:48 +00:00
req . Header . Add ( "Content-type" , "application/json" )
2013-05-15 03:27:15 +00:00
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 18:00:22 +00:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-15 03:27:15 +00:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-10 18:21:56 +00:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-15 03:27:15 +00:00
2013-05-15 19:22:08 +00:00
res , err := r . client . Do ( req )
2013-05-15 03:27:15 +00:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
// Redirect if necessary
for res . StatusCode >= 300 && res . StatusCode < 400 {
2013-10-26 00:50:40 +00:00
utils . Debugf ( "Redirected to %s" , res . Header . Get ( "Location" ) )
2013-07-03 15:24:43 +00:00
req , err = r . reqFactory . NewRequest ( "PUT" , res . Header . Get ( "Location" ) , bytes . NewReader ( imgListJSON ) )
2013-05-15 03:27:15 +00:00
if err != nil {
return nil , err
}
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 18:00:22 +00:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-15 03:27:15 +00:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-10 18:21:56 +00:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-15 19:22:08 +00:00
res , err = r . client . Do ( req )
2013-05-15 03:27:15 +00:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
}
2013-05-16 21:33:29 +00:00
var tokens , endpoints [ ] string
2013-07-05 19:20:58 +00:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-16 21:33:29 +00:00
if ! validate {
if res . StatusCode != 200 && res . StatusCode != 201 {
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
return nil , err
}
2013-07-24 03:01:24 +00:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Error: Status %d trying to push repository %s: %s" , res . StatusCode , remote , errBody ) , res )
2013-05-16 21:33:29 +00: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-15 03:27:15 +00:00
}
2013-05-16 21:33:29 +00:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 19:20:58 +00: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 21:33:29 +00:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-05-15 03:27:15 +00:00
}
if validate {
if res . StatusCode != 204 {
2013-06-04 13:51:12 +00:00
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-15 03:27:15 +00:00
return nil , err
}
2013-07-24 03:01:24 +00:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Error: Status %d trying to push checksums %s: %s" , res . StatusCode , remote , errBody ) , res )
2013-05-15 03:27:15 +00:00
}
}
return & RepositoryData {
Tokens : tokens ,
Endpoints : endpoints ,
} , nil
}
2013-05-15 01:41:39 +00:00
2013-05-15 18:50:52 +00:00
func ( r * Registry ) SearchRepositories ( term string ) ( * SearchResults , error ) {
2013-10-22 18:49:13 +00:00
utils . Debugf ( "Index server: %s" , r . indexEndpoint )
2013-07-05 19:20:58 +00:00
u := auth . IndexServerAddress ( ) + "search?q=" + url . QueryEscape ( term )
2013-08-02 07:08:08 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , u , nil )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
2014-01-21 04:06:19 +00:00
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 19:22:08 +00:00
res , err := r . client . Do ( req )
2013-05-15 01:41:39 +00:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-24 03:01:24 +00:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Unexepected status code %d" , res . StatusCode ) , res )
2013-05-15 01:41:39 +00: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 14:23:43 +00:00
func ( r * Registry ) GetAuthConfig ( withPasswd bool ) * auth . AuthConfig {
password := ""
if withPasswd {
password = r . authConfig . Password
}
2013-05-16 00:17:33 +00:00
return & auth . AuthConfig {
Username : r . authConfig . Username ,
2013-05-24 14:23:43 +00:00
Password : password ,
2013-05-16 00:31:11 +00:00
Email : r . authConfig . Email ,
2013-05-16 00:17:33 +00:00
}
}
2013-10-24 19:20:34 +00: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-15 01:41:39 +00:00
type SearchResults struct {
2013-10-24 19:20:34 +00:00
Query string ` json:"query" `
NumResults int ` json:"num_results" `
Results [ ] SearchResult ` json:"results" `
2013-05-15 01:41:39 +00:00
}
type RepositoryData struct {
ImgList map [ string ] * ImgData
Endpoints [ ] string
Tokens [ ] string
}
type ImgData struct {
2014-02-24 02:33:46 +00:00
ID string ` json:"id" `
Checksum string ` json:"checksum,omitempty" `
Tag string ` json:",omitempty" `
2014-02-24 20:40:33 +00:00
checksumPayload string
2013-05-15 01:41:39 +00:00
}
type Registry struct {
2013-10-22 18:57:48 +00:00
client * http . Client
authConfig * auth . AuthConfig
reqFactory * utils . HTTPRequestFactory
2013-10-22 18:49:13 +00:00
indexEndpoint string
2013-06-28 23:29:02 +00:00
}
2013-10-22 18:49:13 +00:00
func NewRegistry ( authConfig * auth . AuthConfig , factory * utils . HTTPRequestFactory , indexEndpoint string ) ( r * Registry , err error ) {
2013-06-03 21:42:21 +00:00
httpTransport := & http . Transport {
DisableKeepAlives : true ,
2013-06-07 01:16:16 +00:00
Proxy : http . ProxyFromEnvironment ,
2013-06-03 21:42:21 +00:00
}
2013-06-17 18:13:40 +00:00
r = & Registry {
2013-05-15 01:41:39 +00:00
authConfig : authConfig ,
2013-06-03 21:42:21 +00:00
client : & http . Client {
Transport : httpTransport ,
} ,
2013-10-22 18:49:13 +00:00
indexEndpoint : indexEndpoint ,
2013-05-15 01:41:39 +00:00
}
2013-06-17 18:13:40 +00:00
r . client . Jar , err = cookiejar . New ( nil )
2013-06-28 21:12:12 +00:00
if err != nil {
return nil , err
}
2013-08-02 07:08:08 +00:00
2014-01-02 16:51:42 +00:00
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
2013-10-22 18:49:13 +00:00
// alongside our requests.
if indexEndpoint != auth . IndexServerAddress ( ) && strings . HasPrefix ( indexEndpoint , "https://" ) {
2014-01-02 16:51:42 +00:00
standalone , err := pingRegistryEndpoint ( indexEndpoint )
if err != nil {
return nil , err
}
if standalone {
utils . Debugf ( "Endpoint %s is eligible for private registry auth. Enabling decorator." , indexEndpoint )
dec := utils . NewHTTPAuthDecorator ( authConfig . Username , authConfig . Password )
factory . AddDecorator ( dec )
}
2013-10-22 18:49:13 +00:00
}
2013-08-02 07:08:08 +00:00
r . reqFactory = factory
2013-06-28 21:12:12 +00:00
return r , nil
2013-05-15 01:41:39 +00:00
}