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"
2014-05-05 17:29:20 +00:00
_ "crypto/sha512"
2013-12-04 14:03:51 +00:00
"crypto/tls"
"crypto/x509"
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"
"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-12-04 14:03:51 +00:00
"os"
"path"
2013-07-05 19:20:58 +00:00
"regexp"
2014-04-27 22:06:09 +00:00
"runtime"
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"
2014-04-29 09:01:07 +00:00
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/dockerversion"
2014-07-28 15:01:21 +00:00
"github.com/docker/docker/pkg/httputils"
2014-07-24 22:19:50 +00:00
"github.com/docker/docker/utils"
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
2013-12-04 14:03:51 +00:00
type TimeoutType uint32
const (
NoTimeout TimeoutType = iota
ReceiveTimeout
ConnectTimeout
)
func newClient ( jar http . CookieJar , roots * x509 . CertPool , cert * tls . Certificate , timeout TimeoutType ) * http . Client {
tlsConfig := tls . Config { RootCAs : roots }
if cert != nil {
tlsConfig . Certificates = append ( tlsConfig . Certificates , * cert )
}
httpTransport := & http . Transport {
DisableKeepAlives : true ,
Proxy : http . ProxyFromEnvironment ,
TLSClientConfig : & tlsConfig ,
}
switch timeout {
case ConnectTimeout :
httpTransport . Dial = func ( proto string , addr string ) ( net . Conn , error ) {
// Set the connect timeout to 5 seconds
conn , err := net . DialTimeout ( proto , addr , 5 * time . Second )
if err != nil {
return nil , err
}
// Set the recv timeout to 10 seconds
conn . SetDeadline ( time . Now ( ) . Add ( 10 * time . Second ) )
return conn , nil
}
case ReceiveTimeout :
httpTransport . Dial = func ( proto string , addr string ) ( net . Conn , error ) {
conn , err := net . Dial ( proto , addr )
if err != nil {
return nil , err
}
conn = utils . NewTimeoutConn ( conn , 1 * time . Minute )
return conn , nil
}
}
return & http . Client {
Transport : httpTransport ,
CheckRedirect : AddRequiredHeadersToRedirectedRequests ,
Jar : jar ,
}
}
func doRequest ( req * http . Request , jar http . CookieJar , timeout TimeoutType ) ( * http . Response , * http . Client , error ) {
hasFile := func ( files [ ] os . FileInfo , name string ) bool {
for _ , f := range files {
if f . Name ( ) == name {
return true
}
}
return false
}
hostDir := path . Join ( "/etc/docker/certs.d" , req . URL . Host )
fs , err := ioutil . ReadDir ( hostDir )
if err != nil && ! os . IsNotExist ( err ) {
return nil , nil , err
}
var (
pool * x509 . CertPool
certs [ ] * tls . Certificate
)
for _ , f := range fs {
if strings . HasSuffix ( f . Name ( ) , ".crt" ) {
if pool == nil {
pool = x509 . NewCertPool ( )
}
data , err := ioutil . ReadFile ( path . Join ( hostDir , f . Name ( ) ) )
if err != nil {
return nil , nil , err
} else {
pool . AppendCertsFromPEM ( data )
}
}
if strings . HasSuffix ( f . Name ( ) , ".cert" ) {
certName := f . Name ( )
keyName := certName [ : len ( certName ) - 5 ] + ".key"
if ! hasFile ( fs , keyName ) {
return nil , nil , fmt . Errorf ( "Missing key %s for certificate %s" , keyName , certName )
} else {
cert , err := tls . LoadX509KeyPair ( path . Join ( hostDir , certName ) , path . Join ( hostDir , keyName ) )
if err != nil {
return nil , nil , err
}
certs = append ( certs , & cert )
}
}
if strings . HasSuffix ( f . Name ( ) , ".key" ) {
keyName := f . Name ( )
certName := keyName [ : len ( keyName ) - 4 ] + ".cert"
if ! hasFile ( fs , certName ) {
return nil , nil , fmt . Errorf ( "Missing certificate %s for key %s" , certName , keyName )
}
}
}
if len ( certs ) == 0 {
client := newClient ( jar , pool , nil , timeout )
res , err := client . Do ( req )
if err != nil {
return nil , nil , err
}
return res , client , nil
} else {
for i , cert := range certs {
client := newClient ( jar , pool , cert , timeout )
res , err := client . Do ( req )
if i == len ( certs ) - 1 {
// If this is the last cert, always return the result
return res , client , err
} else {
// Otherwise, continue to next cert if 403 or 5xx
if err == nil && res . StatusCode != 403 && ! ( res . StatusCode >= 500 && res . StatusCode < 600 ) {
return res , client , err
}
}
}
}
return nil , nil , nil
}
2014-03-12 03:36:51 +00:00
func pingRegistryEndpoint ( endpoint string ) ( RegistryInfo , error ) {
2014-03-11 00:16:58 +00:00
if endpoint == IndexServerAddress ( ) {
2013-07-09 18:30:12 +00:00
// Skip the check, we now this one is valid
// (and we never want to fallback to http in case of error)
2014-03-12 03:36:51 +00:00
return RegistryInfo { Standalone : false } , nil
2013-07-09 18:30:12 +00:00
}
2013-12-04 14:03:51 +00:00
req , err := http . NewRequest ( "GET" , endpoint + "_ping" , nil )
if err != nil {
return RegistryInfo { Standalone : false } , err
2014-03-27 19:31:04 +00:00
}
2013-12-04 14:03:51 +00:00
resp , _ , err := doRequest ( req , nil , ConnectTimeout )
2013-06-28 00:55:17 +00:00
if err != nil {
2014-03-12 03:36:51 +00:00
return RegistryInfo { Standalone : false } , err
2013-07-05 19:20:58 +00:00
}
2013-12-04 14:03:51 +00:00
2013-11-30 00:20:59 +00:00
defer resp . Body . Close ( )
2013-11-29 10:02:53 +00:00
2014-03-12 03:36:51 +00:00
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return RegistryInfo { Standalone : false } , fmt . Errorf ( "Error while reading the http response: %s" , err )
2013-07-05 19:20:58 +00:00
}
2014-03-10 20:11:03 +00:00
2014-03-12 03:36:51 +00:00
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
info := RegistryInfo {
Standalone : true ,
2014-03-10 20:11:03 +00:00
}
2014-03-12 03:36:51 +00:00
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
utils . Debugf ( "Error unmarshalling the _ping RegistryInfo: %s" , err )
// don't stop here. Just assume sane defaults
2014-03-10 20:11:03 +00:00
}
2014-03-12 03:36:51 +00:00
if hdr := resp . Header . Get ( "X-Docker-Registry-Version" ) ; hdr != "" {
utils . Debugf ( "Registry version header: '%s'" , hdr )
info . Version = hdr
2013-07-05 19:20:58 +00:00
}
2014-03-12 03:36:51 +00:00
utils . Debugf ( "RegistryInfo.Version: %q" , info . Version )
2014-01-02 16:51:42 +00:00
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
utils . Debugf ( "Registry standalone header: '%s'" , standalone )
2014-03-12 03:36:51 +00:00
// Accepted values are "true" (case-insensitive) and "1".
if strings . EqualFold ( standalone , "true" ) || standalone == "1" {
info . Standalone = true
} else if len ( standalone ) > 0 {
// there is a header set, and it is not "true" or "1", so assume fails
info . Standalone = false
}
utils . Debugf ( "RegistryInfo.Standalone: %q" , info . Standalone )
return info , 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
}
2014-02-20 22:57:58 +00:00
// Resolves a repository name to a hostname + name
2013-07-05 19:20:58 +00:00
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 )
2014-04-14 23:15:38 +00:00
if len ( nameParts ) == 1 || ( ! 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 )
2014-03-11 00:16:58 +00:00
return IndexServerAddress ( ) , reposName , err
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
}
2014-02-20 22:57:58 +00:00
return hostname , reposName , nil
2013-09-03 18:45:49 +00:00
}
// 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-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-12-04 14:03:51 +00:00
func ( r * Registry ) doRequest ( req * http . Request ) ( * http . Response , * http . Client , error ) {
return doRequest ( req , r . jar , r . timeout )
}
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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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
2014-03-10 20:11:03 +00:00
// if the size header is not present, then set it to '-1'
imageSize := - 1
if hdr := res . Header . Get ( "X-Docker-Size" ) ; hdr != "" {
imageSize , err = strconv . Atoi ( hdr )
if err != nil {
return nil , - 1 , err
}
2013-06-07 01:16:16 +00:00
}
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
}
2014-03-26 00:33:17 +00:00
func ( r * Registry ) GetRemoteImageLayer ( imgID , registry string , token [ ] string , imgSize int64 ) ( io . ReadCloser , error ) {
var (
2014-06-27 12:10:30 +00:00
retries = 5
client * http . Client
res * http . Response
imageURL = fmt . Sprintf ( "%simages/%s/layer" , registry , imgID )
2014-03-26 00:33:17 +00:00
)
2014-06-27 12:10:30 +00:00
req , err := r . reqFactory . NewRequest ( "GET" , imageURL , nil )
2014-03-26 00:33:17 +00:00
if err != nil {
return nil , fmt . Errorf ( "Error while getting from the server: %s\n" , err )
}
2014-06-27 12:10:30 +00:00
setTokenAuth ( req , token )
2014-03-26 00:33:17 +00:00
for i := 1 ; i <= retries ; i ++ {
2014-06-27 12:10:30 +00:00
res , client , err = r . doRequest ( req )
if err != nil {
res . Body . Close ( )
if i == retries {
return nil , fmt . Errorf ( "Server error: Status %d while fetching image layer (%s)" ,
res . StatusCode , imgID )
}
2014-03-26 00:33:17 +00:00
time . Sleep ( time . Duration ( i ) * 5 * time . Second )
continue
}
break
}
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 )
}
2014-06-27 12:10:30 +00:00
if res . Header . Get ( "Accept-Ranges" ) == "bytes" && imgSize > 0 {
utils . Debugf ( "server supports resume" )
2014-07-28 15:01:21 +00:00
return httputils . ResumableRequestReaderWithInitialResponse ( client , req , 5 , imgSize , res ) , nil
2014-06-27 12:10:30 +00:00
}
utils . Debugf ( "server doesn't support resume" )
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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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" )
}
2014-04-14 18:32:47 +00:00
func buildEndpointsList ( headers [ ] string , indexEp string ) ( [ ] string , error ) {
var endpoints [ ] string
parsedUrl , err := url . Parse ( indexEp )
if err != nil {
return nil , err
}
var urlScheme = parsedUrl . Scheme
// The Registry's URL scheme has to match the Index'
for _ , ep := range headers {
epList := strings . Split ( ep , "," )
for _ , epListElement := range epList {
endpoints = append (
endpoints ,
fmt . Sprintf ( "%s://%s/v1/" , urlScheme , strings . TrimSpace ( epListElement ) ) )
}
}
return endpoints , nil
}
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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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
if res . Header . Get ( "X-Docker-Endpoints" ) != "" {
2014-04-14 18:32:47 +00:00
endpoints , err = buildEndpointsList ( res . Header [ "X-Docker-Endpoints" ] , indexEp )
if err != nil {
return nil , err
2013-07-05 19:20:58 +00:00
}
2013-05-15 01:41:39 +00:00
} else {
2014-03-10 20:11:03 +00:00
// Assume the endpoint is on the same host
2014-04-29 09:01:07 +00:00
u , err := url . Parse ( indexEp )
if err != nil {
return nil , err
}
endpoints = append ( endpoints , fmt . Sprintf ( "%s://%s/v1/" , u . Scheme , req . URL . Host ) )
2013-05-15 01:41:39 +00:00
}
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-26 00:06:04 +00:00
req . Header . Set ( "X-Docker-Checksum-Payload" , imgData . ChecksumPayload )
2013-07-17 19:13:22 +00:00
2013-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( req )
2013-07-17 19:13:22 +00:00
if err != nil {
return fmt . Errorf ( "Failed to upload metadata: %s" , err )
}
defer res . Body . Close ( )
if len ( res . Cookies ( ) ) > 0 {
2013-12-04 14:03:51 +00:00
r . jar . SetCookies ( req . URL , res . Cookies ( ) )
2013-07-17 19:13:22 +00:00
}
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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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 ( )
2014-04-08 14:53:16 +00:00
if res . StatusCode == 401 && strings . HasPrefix ( registry , "http://" ) {
return utils . NewHTTPRequestError ( "HTTP code 401, Docker will not send auth headers over HTTP." , res )
}
2013-05-15 03:27:15 +00:00
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-04-01 00:56:25 +00:00
tarsumLayer := & utils . TarSum { Reader : layer }
2014-04-01 01:31:15 +00:00
h := sha256 . New ( )
h . Write ( jsonRaw )
h . Write ( [ ] byte { '\n' } )
2014-04-01 00:56:25 +00:00
checksumLayer := & utils . CheckSum { Reader : tarsumLayer , Hash : h }
2013-07-22 21:50:32 +00:00
2014-04-01 00:56:25 +00:00
req , err := r . reqFactory . NewRequest ( "PUT" , registry + "images/" + imgID + "/layer" , checksumLayer )
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
}
2014-07-16 10:22:13 +00:00
req . Header . Add ( "Content-Type" , "application/octet-stream" )
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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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
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" ) != "" {
2014-04-14 18:32:47 +00:00
endpoints , err = buildEndpointsList ( res . Header [ "X-Docker-Endpoints" ] , indexEp )
if err != nil {
return nil , err
2013-07-05 19:20:58 +00:00
}
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 )
2014-03-13 17:40:34 +00:00
u := r . indexEndpoint + "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-12-04 14:03:51 +00:00
res , _ , err := r . doRequest ( 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
}
2014-03-11 00:16:58 +00:00
func ( r * Registry ) GetAuthConfig ( withPasswd bool ) * AuthConfig {
2013-05-24 14:23:43 +00:00
password := ""
if withPasswd {
password = r . authConfig . Password
}
2014-03-11 00:16:58 +00:00
return & AuthConfig {
2013-05-16 00:17:33 +00:00
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" `
2014-02-26 00:06:04 +00:00
ChecksumPayload string ` json:"-" `
2014-02-24 02:33:46 +00:00
Tag string ` json:",omitempty" `
2013-05-15 01:41:39 +00:00
}
2014-03-12 03:36:51 +00:00
type RegistryInfo struct {
Version string ` json:"version" `
Standalone bool ` json:"standalone" `
}
2013-05-15 01:41:39 +00:00
type Registry struct {
2014-03-11 00:16:58 +00:00
authConfig * AuthConfig
2013-10-22 18:57:48 +00:00
reqFactory * utils . HTTPRequestFactory
2013-10-22 18:49:13 +00:00
indexEndpoint string
2013-12-04 14:03:51 +00:00
jar * cookiejar . Jar
timeout TimeoutType
2013-06-28 23:29:02 +00:00
}
2014-06-05 18:37:37 +00:00
func trustedLocation ( req * http . Request ) bool {
var (
trusteds = [ ] string { "docker.com" , "docker.io" }
hostname = strings . SplitN ( req . Host , ":" , 2 ) [ 0 ]
)
if req . URL . Scheme != "https" {
return false
}
for _ , trusted := range trusteds {
2014-06-07 21:17:56 +00:00
if hostname == trusted || strings . HasSuffix ( hostname , "." + trusted ) {
2014-06-05 18:37:37 +00:00
return true
}
}
return false
}
2014-03-26 00:33:17 +00:00
func AddRequiredHeadersToRedirectedRequests ( req * http . Request , via [ ] * http . Request ) error {
if via != nil && via [ 0 ] != nil {
2014-06-05 18:37:37 +00:00
if trustedLocation ( req ) && trustedLocation ( via [ 0 ] ) {
req . Header = via [ 0 ] . Header
} else {
for k , v := range via [ 0 ] . Header {
if k != "Authorization" {
for _ , vv := range v {
req . Header . Add ( k , vv )
}
}
}
}
2014-03-26 00:33:17 +00:00
}
return nil
}
2014-06-07 23:48:25 +00:00
func NewRegistry ( authConfig * AuthConfig , factory * utils . HTTPRequestFactory , indexEndpoint string , timeout bool ) ( r * Registry , err error ) {
2013-06-17 18:13:40 +00:00
r = & Registry {
2013-12-04 14:03:51 +00:00
authConfig : authConfig ,
2013-10-22 18:49:13 +00:00
indexEndpoint : indexEndpoint ,
2013-05-15 01:41:39 +00:00
}
2014-05-23 06:58:56 +00:00
2013-12-04 14:03:51 +00:00
if timeout {
r . timeout = ReceiveTimeout
}
r . 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.
2014-03-11 00:16:58 +00:00
if indexEndpoint != IndexServerAddress ( ) && strings . HasPrefix ( indexEndpoint , "https://" ) {
2014-03-12 03:36:51 +00:00
info , err := pingRegistryEndpoint ( indexEndpoint )
2014-01-02 16:51:42 +00:00
if err != nil {
return nil , err
}
2014-03-12 03:36:51 +00:00
if info . Standalone {
2014-03-11 00:16:58 +00:00
utils . Debugf ( "Endpoint %s is eligible for private registry registry. Enabling decorator." , indexEndpoint )
2014-01-02 16:51:42 +00:00
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
}
2014-04-27 22:06:09 +00:00
func HTTPRequestFactory ( metaHeaders map [ string ] [ ] string ) * utils . HTTPRequestFactory {
// FIXME: this replicates the 'info' job.
httpVersion := make ( [ ] utils . VersionInfo , 0 , 4 )
httpVersion = append ( httpVersion , & simpleVersionInfo { "docker" , dockerversion . VERSION } )
httpVersion = append ( httpVersion , & simpleVersionInfo { "go" , runtime . Version ( ) } )
httpVersion = append ( httpVersion , & simpleVersionInfo { "git-commit" , dockerversion . GITCOMMIT } )
if kernelVersion , err := utils . GetKernelVersion ( ) ; err == nil {
httpVersion = append ( httpVersion , & simpleVersionInfo { "kernel" , kernelVersion . String ( ) } )
}
httpVersion = append ( httpVersion , & simpleVersionInfo { "os" , runtime . GOOS } )
httpVersion = append ( httpVersion , & simpleVersionInfo { "arch" , runtime . GOARCH } )
ud := utils . NewHTTPUserAgentDecorator ( httpVersion ... )
md := & utils . HTTPMetaHeadersDecorator {
Headers : metaHeaders ,
}
factory := utils . NewHTTPRequestFactory ( ud , md )
return factory
}
// simpleVersionInfo is a simple implementation of
// the interface VersionInfo, which is used
// to provide version information for some product,
// component, etc. It stores the product name and the version
// in string and returns them on calls to Name() and Version().
type simpleVersionInfo struct {
name string
version string
}
func ( v * simpleVersionInfo ) Name ( ) string {
return v . name
}
func ( v * simpleVersionInfo ) Version ( ) string {
return v . version
}