2013-05-15 05:41:39 +04:00
package registry
import (
2013-05-15 07:27:15 +04:00
"bytes"
2013-05-15 05:41:39 +04:00
"encoding/json"
2013-05-16 00:22:57 +04:00
"errors"
2013-05-15 05:41:39 +04:00
"fmt"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"net/http"
2013-06-17 22:13:40 +04:00
"net/http/cookiejar"
2013-05-15 05:41:39 +04:00
"net/url"
2013-07-05 23:20:58 +04:00
"regexp"
2013-06-07 05:16:16 +04:00
"strconv"
2013-05-15 05:41:39 +04:00
"strings"
)
2013-07-23 01:50:32 +04:00
var (
ErrAlreadyExists = errors . New ( "Image already exists" )
ErrInvalidRepositoryName = errors . New ( "Invalid repository name (ex: \"registry.domain.tld/myrepos\")" )
)
2013-05-16 00:22:57 +04:00
2013-07-05 23:20:58 +04:00
func pingRegistryEndpoint ( endpoint string ) error {
2013-07-09 22: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-07-06 01:55:48 +04:00
resp , err := http . Get ( endpoint + "_ping" )
2013-06-28 04:55:17 +04:00
if err != nil {
2013-07-05 23:20:58 +04:00
return err
}
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-06 01: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 23: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 )
}
validRepo := regexp . MustCompile ( ` ^([a-zA-Z0-9-_.]+)$ ` )
if ! validRepo . MatchString ( name ) {
return fmt . Errorf ( "Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed" , name )
}
return nil
}
// Resolves a repository name to a endpoint + name
func ResolveRepositoryName ( reposName string ) ( string , string , error ) {
2013-07-09 22:30:12 +04:00
if strings . Contains ( reposName , "://" ) {
// It cannot contain a scheme!
return "" , "" , ErrInvalidRepositoryName
}
2013-07-05 23:20:58 +04:00
nameParts := strings . SplitN ( reposName , "/" , 2 )
2013-07-10 03:46:55 +04:00
if ! strings . Contains ( nameParts [ 0 ] , "." ) && ! strings . Contains ( nameParts [ 0 ] , ":" ) {
2013-07-05 23:20:58 +04:00
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
2013-07-06 01:30:43 +04:00
err := validateRepositoryName ( reposName )
2013-07-09 22:30:12 +04:00
return auth . IndexServerAddress ( ) , reposName , err
2013-07-05 23: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 22:30:12 +04:00
return "" , "" , ErrInvalidRepositoryName
2013-07-05 23:20:58 +04:00
}
hostname := nameParts [ 0 ]
2013-07-06 01:30:43 +04:00
reposName = nameParts [ 1 ]
2013-07-10 03: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-07-06 01:30:43 +04:00
endpoint := fmt . Sprintf ( "https://%s/v1/" , hostname )
2013-07-05 23: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-06 01:30:43 +04:00
endpoint = fmt . Sprintf ( "http://%s/v1/" , hostname )
2013-07-05 23:20:58 +04:00
if err = pingRegistryEndpoint ( endpoint ) ; err != nil {
//TODO: triggering highland build can be done there without "failing"
return "" , "" , errors . New ( "Invalid Registry endpoint: " + err . Error ( ) )
}
2013-06-28 04:55:17 +04:00
}
2013-07-06 01:30:43 +04:00
err := validateRepositoryName ( reposName )
2013-07-05 23:20:58 +04:00
return endpoint , reposName , err
2013-06-28 04:55:17 +04:00
}
2013-07-24 01:05:13 +04:00
// VersionInfo is used to model entities which has a version.
2013-06-29 01:12:12 +04:00
// It is basically a tupple with name and version.
2013-07-24 01:05:13 +04:00
type VersionInfo interface {
2013-06-29 01:12:12 +04:00
Name ( ) string
Version ( ) string
}
2013-06-29 01: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-29 01:12:12 +04:00
}
2013-07-23 22: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-29 01:24:54 +04:00
}
2013-06-29 01:12:12 +04:00
2013-07-18 22:22:49 +04:00
// Set the user agent field in the header based on the versions provided
// in NewRegistry() and extra.
2013-07-24 01:05:13 +04:00
func ( r * Registry ) setUserAgent ( req * http . Request , extra ... VersionInfo ) {
2013-06-29 01:24:54 +04:00
if len ( r . baseVersions ) + len ( extra ) == 0 {
return
}
2013-07-02 01:57:56 +04:00
if len ( extra ) == 0 {
req . Header . Set ( "User-Agent" , r . baseVersionsStr )
} else {
req . Header . Set ( "User-Agent" , appendVersions ( r . baseVersionsStr , extra ... ) )
}
2013-06-29 01:24:54 +04:00
return
2013-05-15 05: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 23:37:07 +04:00
func ( r * Registry ) GetRemoteHistory ( imgID , registry string , token [ ] string ) ( [ ] string , error ) {
req , err := http . NewRequest ( "GET" , registry + "images/" + imgID + "/ancestry" , nil )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , ", " ) )
2013-06-29 02:45:45 +04:00
r . setUserAgent ( req )
2013-07-23 22:37:13 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 05:41:39 +04:00
if err != nil || res . StatusCode != 200 {
if res != nil {
2013-07-24 07:01:24 +04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Internal server error: %d trying to fetch remote history for %s" , res . StatusCode , imgID ) , res )
2013-05-15 05:41:39 +04:00
}
return nil , err
}
defer res . Body . Close ( )
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-15 23:22:08 +04:00
return nil , fmt . Errorf ( "Error while reading the http response: %s" , err )
2013-05-15 05: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-03 02:27:22 +04:00
func ( r * Registry ) LookupRemoteImage ( imgID , registry string , token [ ] string ) bool {
2013-05-15 05:41:39 +04:00
rt := & http . Transport { Proxy : http . ProxyFromEnvironment }
2013-07-05 23:37:07 +04:00
req , err := http . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-15 05:41:39 +04:00
if err != nil {
return false
}
res , err := rt . RoundTrip ( req )
2013-06-03 23:20:52 +04:00
if err != nil {
return false
2013-06-03 23:14:57 +04:00
}
2013-06-03 23:20:52 +04:00
res . Body . Close ( )
2013-05-24 21:37:34 +04:00
return res . StatusCode == 200
2013-05-15 05:41:39 +04:00
}
// Retrieve an image from the Registry.
2013-07-03 02:27:22 +04:00
func ( r * Registry ) GetRemoteImageJSON ( imgID , registry string , token [ ] string ) ( [ ] byte , int , error ) {
2013-06-04 22:00:22 +04:00
// Get the JSON
2013-07-05 23:37:07 +04:00
req , err := http . NewRequest ( "GET" , registry + "images/" + imgID + "/json" , nil )
2013-05-15 05:41:39 +04:00
if err != nil {
2013-06-07 05:16:16 +04:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-15 05:41:39 +04:00
}
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , ", " ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-07-23 22:37:13 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 05:41:39 +04:00
if err != nil {
2013-06-07 05:16:16 +04:00
return nil , - 1 , fmt . Errorf ( "Failed to download json: %s" , err )
2013-05-15 05:41:39 +04:00
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-24 07:01:24 +04:00
return nil , - 1 , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d" , res . StatusCode ) , res )
2013-05-15 05:41:39 +04:00
}
2013-06-07 05:16:16 +04:00
imageSize , err := strconv . Atoi ( res . Header . Get ( "X-Docker-Size" ) )
if err != nil {
return nil , - 1 , err
}
2013-05-15 05:41:39 +04:00
jsonString , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-06-07 05:16:16 +04:00
return nil , - 1 , fmt . Errorf ( "Failed to parse downloaded json: %s (%s)" , err , jsonString )
2013-05-15 05:41:39 +04:00
}
2013-06-07 05:16:16 +04:00
return jsonString , imageSize , nil
2013-05-15 05:41:39 +04:00
}
2013-07-05 23:37:07 +04:00
func ( r * Registry ) GetRemoteImageLayer ( imgID , registry string , token [ ] string ) ( io . ReadCloser , error ) {
req , err := http . NewRequest ( "GET" , registry + "images/" + imgID + "/layer" , nil )
2013-05-15 05:41:39 +04:00
if err != nil {
2013-06-07 05:16:16 +04:00
return nil , fmt . Errorf ( "Error while getting from the server: %s\n" , err )
2013-05-15 05:41:39 +04:00
}
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , ", " ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-07-23 22:37:13 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 05:41:39 +04:00
if err != nil {
2013-06-07 05:16:16 +04:00
return nil , err
2013-05-15 05:41:39 +04:00
}
2013-06-07 05:16:16 +04:00
return res . Body , nil
2013-05-15 05:41:39 +04:00
}
2013-05-15 22:50:52 +04:00
func ( r * Registry ) GetRemoteTags ( registries [ ] string , repository string , token [ ] string ) ( map [ string ] string , error ) {
2013-05-15 05: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 23:20:58 +04:00
endpoint := fmt . Sprintf ( "%srepositories/%s/tags" , host , repository )
2013-06-20 00:48:49 +04:00
req , err := r . opaqueRequest ( "GET" , endpoint , nil )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , ", " ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-07-23 22:37:13 +04:00
res , err := doWithCookies ( r . client , req )
2013-06-03 23:20:52 +04:00
if err != nil {
return nil , err
}
2013-05-29 22:24:50 +04:00
2013-06-19 22:07:36 +04:00
utils . Debugf ( "Got status code %d from %s" , res . StatusCode , endpoint )
2013-06-03 23:20:52 +04:00
defer res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 404 {
2013-05-15 05: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 22:00:22 +04:00
rawJSON , err := ioutil . ReadAll ( res . Body )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
2013-06-04 22:00:22 +04:00
if err := json . Unmarshal ( rawJSON , & result ) ; err != nil {
2013-05-15 05:41:39 +04:00
return nil , err
}
return result , nil
}
return nil , fmt . Errorf ( "Could not reach any registry endpoint" )
}
2013-07-05 23:20:58 +04:00
func ( r * Registry ) GetRepositoryData ( indexEp , remote string ) ( * RepositoryData , error ) {
2013-07-23 01:50:32 +04:00
2013-07-05 23:20:58 +04:00
repositoryTarget := fmt . Sprintf ( "%srepositories/%s/images" , indexEp , remote )
2013-05-15 05:41:39 +04:00
2013-07-23 01:50:32 +04:00
utils . Debugf ( "[registry] Calling GET %s" , repositoryTarget )
2013-06-20 00:48:49 +04:00
req , err := r . opaqueRequest ( "GET" , repositoryTarget , nil )
2013-05-15 05: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-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-05-15 05:41:39 +04:00
2013-05-15 23:22:08 +04:00
res , err := r . client . Do ( req )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode == 401 {
2013-07-24 07:01:24 +04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Please login first (HTTP code %d)" , res . StatusCode ) , res )
2013-05-15 05: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-24 07:01:24 +04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code: %d" , res . StatusCode ) , res )
2013-05-15 05: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 23:20:58 +04:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-15 05:41:39 +04:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 23: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-15 05:41:39 +04:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-06-04 22:00:22 +04:00
checksumsJSON , err := ioutil . ReadAll ( res . Body )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
remoteChecksums := [ ] * ImgData { }
2013-06-04 22:00:22 +04:00
if err := json . Unmarshal ( checksumsJSON , & remoteChecksums ) ; err != nil {
2013-05-15 05: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 22:00:22 +04:00
imgsData [ elem . ID ] = elem
2013-05-15 05:41:39 +04:00
}
return & RepositoryData {
ImgList : imgsData ,
Endpoints : endpoints ,
Tokens : tokens ,
} , nil
}
2013-07-17 23: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" )
req , err := http . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/checksum" , nil )
if err != nil {
return err
}
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
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-15 07:27:15 +04:00
// Push a local image to the registry
2013-06-04 22:00:22 +04:00
func ( r * Registry ) PushImageJSONRegistry ( imgData * ImgData , jsonRaw [ ] byte , registry string , token [ ] string ) error {
2013-07-17 23:13:22 +04:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgData . ID + "/json" )
2013-07-23 22:37:13 +04:00
req , err := http . NewRequest ( "PUT" , registry + "images/" + imgData . ID + "/json" , bytes . NewReader ( jsonRaw ) )
2013-05-15 07:27:15 +04:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-05-15 22:30:40 +04:00
2013-05-15 23:22:08 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 07: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-07-24 07:01:24 +04:00
return utils . NewHTTPRequestError ( fmt . Sprint ( "HTTP code %d while uploading metadata and error when trying to parse response body: %s" , res . StatusCode , err ) , res )
2013-05-15 07: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-16 00:22:57 +04:00
return ErrAlreadyExists
2013-05-15 07:27:15 +04:00
}
2013-07-24 07:01:24 +04:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "HTTP code %d while uploading metadata: %s" , res . StatusCode , errBody ) , res )
2013-05-15 07:27:15 +04:00
}
2013-05-15 22:30:40 +04:00
return nil
}
2013-05-15 07:27:15 +04:00
2013-07-23 01: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 23:13:22 +04:00
utils . Debugf ( "[registry] Calling PUT %s" , registry + "images/" + imgID + "/layer" )
tarsumLayer := & utils . TarSum { Reader : layer }
2013-07-23 01:50:32 +04:00
2013-07-17 23:13:22 +04:00
req , err := http . NewRequest ( "PUT" , registry + "images/" + imgID + "/layer" , tarsumLayer )
2013-05-15 07:27:15 +04:00
if err != nil {
2013-07-17 23:13:22 +04:00
return "" , err
2013-05-15 07:27:15 +04:00
}
2013-05-15 22:30:40 +04:00
req . ContentLength = - 1
req . TransferEncoding = [ ] string { "chunked" }
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-05-15 23:22:08 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 07:27:15 +04:00
if err != nil {
2013-07-17 23:13:22 +04:00
return "" , fmt . Errorf ( "Failed to upload layer: %s" , err )
2013-05-15 07:27:15 +04:00
}
2013-05-15 22:30:40 +04:00
defer res . Body . Close ( )
2013-05-15 07:27:15 +04:00
2013-05-15 22:30:40 +04:00
if res . StatusCode != 200 {
errBody , err := ioutil . ReadAll ( res . Body )
2013-05-15 07:27:15 +04:00
if err != nil {
2013-07-24 07:01:24 +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-15 07:27:15 +04:00
}
2013-07-24 07:01:24 +04:00
return utils . NewHTTPRequestError ( fmt . Sprintf ( "Received HTTP code %d while uploading layer: %s" , res . StatusCode , errBody ) , res )
2013-05-15 07:27:15 +04:00
}
2013-07-23 01:50:32 +04:00
return tarsumLayer . Sum ( jsonRaw ) , nil
2013-05-15 07:27:15 +04:00
}
2013-06-20 00:48:49 +04:00
func ( r * Registry ) opaqueRequest ( method , urlStr string , body io . Reader ) ( * http . Request , error ) {
req , err := http . NewRequest ( method , urlStr , body )
if err != nil {
return nil , err
}
2013-06-21 01:19:09 +04:00
req . URL . Opaque = strings . Replace ( urlStr , req . URL . Scheme + ":" , "" , 1 )
2013-06-20 00:48:49 +04:00
return req , err
}
2013-05-15 07:27:15 +04:00
// push a tag on the registry.
// Remote has the format '<user>/<repo>
2013-05-15 22:30:40 +04:00
func ( r * Registry ) PushRegistryTag ( remote , revision , tag , registry string , token [ ] string ) error {
2013-05-15 07:27:15 +04:00
// "jsonify" the string
revision = "\"" + revision + "\""
2013-07-05 23:20:58 +04:00
req , err := r . opaqueRequest ( "PUT" , registry + "repositories/" + remote + "/tags/" + tag , strings . NewReader ( revision ) )
2013-05-15 07:27:15 +04:00
if err != nil {
return err
}
req . Header . Add ( "Content-type" , "application/json" )
req . Header . Set ( "Authorization" , "Token " + strings . Join ( token , "," ) )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-05-15 07:27:15 +04:00
req . ContentLength = int64 ( len ( revision ) )
2013-05-15 23:22:08 +04:00
res , err := doWithCookies ( r . client , req )
2013-05-15 07:27:15 +04:00
if err != nil {
return err
}
res . Body . Close ( )
if res . StatusCode != 200 && res . StatusCode != 201 {
2013-07-24 07: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-15 07:27:15 +04:00
}
return nil
}
2013-07-05 23:20:58 +04:00
func ( r * Registry ) PushImageJSONIndex ( indexEp , remote string , imgList [ ] * ImgData , validate bool , regs [ ] string ) ( * RepositoryData , error ) {
2013-07-23 03:44:34 +04:00
cleanImgList := [ ] * ImgData { }
if validate {
for _ , elem := range imgList {
if elem . Checksum != "" {
cleanImgList = append ( cleanImgList , elem )
}
}
} else {
cleanImgList = imgList
}
imgListJSON , err := json . Marshal ( cleanImgList )
2013-05-15 07:27:15 +04:00
if err != nil {
return nil , err
}
2013-05-17 01:33:29 +04:00
var suffix string
if validate {
suffix = "images"
}
2013-05-29 04:12:24 +04:00
2013-07-05 23:20:58 +04:00
u := fmt . Sprintf ( "%srepositories/%s/%s" , indexEp , remote , suffix )
2013-07-23 01:50:32 +04:00
utils . Debugf ( "[registry] PUT %s" , u )
2013-06-04 22:00:22 +04:00
utils . Debugf ( "Image list pushed to index:\n%s\n" , imgListJSON )
2013-07-05 23:20:58 +04:00
req , err := r . opaqueRequest ( "PUT" , u , bytes . NewReader ( imgListJSON ) )
2013-05-15 07:27:15 +04:00
if err != nil {
return nil , err
}
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 22:00:22 +04:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-15 07:27:15 +04:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-06-10 22:21:56 +04:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-15 07:27:15 +04:00
2013-05-15 23:22:08 +04:00
res , err := r . client . Do ( req )
2013-05-15 07:27:15 +04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
// Redirect if necessary
for res . StatusCode >= 300 && res . StatusCode < 400 {
utils . Debugf ( "Redirected to %s\n" , res . Header . Get ( "Location" ) )
2013-06-20 00:48:49 +04:00
req , err = r . opaqueRequest ( "PUT" , res . Header . Get ( "Location" ) , bytes . NewReader ( imgListJSON ) )
2013-05-15 07:27:15 +04:00
if err != nil {
return nil , err
}
req . SetBasicAuth ( r . authConfig . Username , r . authConfig . Password )
2013-06-04 22:00:22 +04:00
req . ContentLength = int64 ( len ( imgListJSON ) )
2013-05-15 07:27:15 +04:00
req . Header . Set ( "X-Docker-Token" , "true" )
2013-06-29 01:48:37 +04:00
r . setUserAgent ( req )
2013-06-10 22:21:56 +04:00
if validate {
req . Header [ "X-Docker-Endpoints" ] = regs
}
2013-05-15 23:22:08 +04:00
res , err = r . client . Do ( req )
2013-05-15 07:27:15 +04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
}
2013-05-17 01:33:29 +04:00
var tokens , endpoints [ ] string
2013-07-05 23:20:58 +04:00
var urlScheme = indexEp [ : strings . Index ( indexEp , ":" ) ]
2013-05-17 01: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-24 07: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-17 01: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-15 07:27:15 +04:00
}
2013-05-17 01:33:29 +04:00
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2013-07-05 23: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-17 01:33:29 +04:00
} else {
return nil , fmt . Errorf ( "Index response didn't contain any endpoints" )
}
2013-05-15 07:27:15 +04:00
}
if validate {
if res . StatusCode != 204 {
2013-06-04 17:51:12 +04:00
errBody , err := ioutil . ReadAll ( res . Body )
if err != nil {
2013-05-15 07:27:15 +04:00
return nil , err
}
2013-07-24 07: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-15 07:27:15 +04:00
}
}
return & RepositoryData {
Tokens : tokens ,
Endpoints : endpoints ,
} , nil
}
2013-05-15 05:41:39 +04:00
2013-05-15 22:50:52 +04:00
func ( r * Registry ) SearchRepositories ( term string ) ( * SearchResults , error ) {
2013-07-05 23:20:58 +04:00
u := auth . IndexServerAddress ( ) + "search?q=" + url . QueryEscape ( term )
2013-05-15 05:41:39 +04:00
req , err := http . NewRequest ( "GET" , u , nil )
if err != nil {
return nil , err
}
2013-05-15 23:22:08 +04:00
res , err := r . client . Do ( req )
2013-05-15 05:41:39 +04:00
if err != nil {
return nil , err
}
defer res . Body . Close ( )
if res . StatusCode != 200 {
2013-07-24 07:01:24 +04:00
return nil , utils . NewHTTPRequestError ( fmt . Sprintf ( "Unexepected status code %d" , res . StatusCode ) , res )
2013-05-15 05: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 18:23:43 +04:00
func ( r * Registry ) GetAuthConfig ( withPasswd bool ) * auth . AuthConfig {
password := ""
if withPasswd {
password = r . authConfig . Password
}
2013-05-16 04:17:33 +04:00
return & auth . AuthConfig {
Username : r . authConfig . Username ,
2013-05-24 18:23:43 +04:00
Password : password ,
2013-05-16 04:31:11 +04:00
Email : r . authConfig . Email ,
2013-05-16 04:17:33 +04:00
}
}
2013-05-15 05:41:39 +04:00
type SearchResults struct {
Query string ` json:"query" `
NumResults int ` json:"num_results" `
Results [ ] map [ string ] string ` json:"results" `
}
type RepositoryData struct {
ImgList map [ string ] * ImgData
Endpoints [ ] string
Tokens [ ] string
}
type ImgData struct {
2013-06-04 22:00:22 +04:00
ID string ` json:"id" `
2013-05-15 05:41:39 +04:00
Checksum string ` json:"checksum,omitempty" `
Tag string ` json:",omitempty" `
}
type Registry struct {
2013-06-29 03:29:02 +04:00
client * http . Client
authConfig * auth . AuthConfig
2013-07-24 01:05:13 +04:00
baseVersions [ ] VersionInfo
2013-06-29 03:29:02 +04:00
baseVersionsStr string
}
2013-07-24 01:05:13 +04:00
func validVersion ( version VersionInfo ) bool {
2013-06-29 03:29:02 +04:00
stopChars := " \t\r\n/"
if strings . ContainsAny ( version . Name ( ) , stopChars ) {
return false
}
if strings . ContainsAny ( version . Version ( ) , stopChars ) {
return false
}
return true
}
2013-07-18 22:22:49 +04:00
// Convert versions to a string and append the string to the string base.
//
2013-07-24 01:05:13 +04:00
// Each VersionInfo will be converted to a string in the format of
2013-07-18 22:22:49 +04:00
// "product/version", where the "product" is get from the Name() method, while
// version is get from the Version() method. Several pieces of verson information
// will be concatinated and separated by space.
2013-07-24 01:05:13 +04:00
func appendVersions ( base string , versions ... VersionInfo ) string {
2013-06-29 03:29:02 +04:00
if len ( versions ) == 0 {
return base
}
var buf bytes . Buffer
if len ( base ) > 0 {
buf . Write ( [ ] byte ( base ) )
}
for _ , v := range versions {
if ! validVersion ( v ) {
continue
}
buf . Write ( [ ] byte ( v . Name ( ) ) )
buf . Write ( [ ] byte ( "/" ) )
buf . Write ( [ ] byte ( v . Version ( ) ) )
buf . Write ( [ ] byte ( " " ) )
}
return buf . String ( )
2013-05-15 05:41:39 +04:00
}
2013-07-24 01:05:13 +04:00
func NewRegistry ( root string , authConfig * auth . AuthConfig , baseVersions ... VersionInfo ) ( r * Registry , err error ) {
2013-06-04 01:42:21 +04:00
httpTransport := & http . Transport {
DisableKeepAlives : true ,
2013-06-07 05:16:16 +04:00
Proxy : http . ProxyFromEnvironment ,
2013-06-04 01:42:21 +04:00
}
2013-06-17 22:13:40 +04:00
r = & Registry {
2013-05-15 05:41:39 +04:00
authConfig : authConfig ,
2013-06-04 01:42:21 +04:00
client : & http . Client {
Transport : httpTransport ,
} ,
2013-05-15 05:41:39 +04:00
}
2013-06-17 22:13:40 +04:00
r . client . Jar , err = cookiejar . New ( nil )
2013-06-29 01:12:12 +04:00
if err != nil {
return nil , err
}
r . baseVersions = baseVersions
2013-06-29 03:29:02 +04:00
r . baseVersionsStr = appendVersions ( "" , baseVersions ... )
2013-06-29 01:12:12 +04:00
return r , nil
2013-05-15 05:41:39 +04:00
}