2014-08-26 19:21:04 -04:00
package registry
import (
2015-02-12 13:23:22 -05:00
"crypto/tls"
2014-08-26 19:21:04 -04:00
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
2015-03-26 18:22:04 -04:00
"github.com/Sirupsen/logrus"
2015-05-17 05:07:48 -04:00
"github.com/docker/distribution/registry/client/transport"
2016-01-04 19:05:26 -05:00
registrytypes "github.com/docker/engine-api/types/registry"
2014-08-26 19:21:04 -04:00
)
2016-03-01 02:07:41 -05:00
// V1Endpoint stores basic information about a V1 registry endpoint.
type V1Endpoint struct {
client * http . Client
URL * url . URL
IsSecure bool
2014-08-26 19:21:04 -04:00
}
2016-03-01 02:07:41 -05:00
// NewV1Endpoint parses the given address to return a registry endpoint. v can be used to
2015-09-16 13:42:17 -04:00
// specify a specific endpoint version
2016-03-01 02:07:41 -05:00
func NewV1Endpoint ( index * registrytypes . IndexInfo , userAgent string , metaHeaders http . Header ) ( * V1Endpoint , error ) {
2015-07-28 13:36:57 -04:00
tlsConfig , err := newTLSConfig ( index . Name , index . Secure )
if err != nil {
return nil , err
}
2016-02-17 19:53:25 -05:00
2016-03-01 02:07:41 -05:00
endpoint , err := newV1EndpointFromStr ( GetAuthConfigKey ( index ) , tlsConfig , userAgent , metaHeaders )
2014-08-26 19:21:04 -04:00
if err != nil {
return nil , err
}
2016-02-17 19:53:25 -05:00
2014-12-23 16:40:06 -05:00
if err := validateEndpoint ( endpoint ) ; err != nil {
return nil , err
}
return endpoint , nil
}
2014-08-26 19:21:04 -04:00
2016-03-01 02:07:41 -05:00
func validateEndpoint ( endpoint * V1Endpoint ) error {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "pinging registry endpoint %s" , endpoint )
2014-12-11 20:55:15 -05:00
2014-10-10 23:22:12 -04:00
// Try HTTPS ping to registry
2014-08-26 19:21:04 -04:00
endpoint . URL . Scheme = "https"
if _ , err := endpoint . Ping ( ) ; err != nil {
2014-12-23 16:40:06 -05:00
if endpoint . IsSecure {
2014-10-10 23:22:12 -04:00
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
2014-12-23 16:40:06 -05:00
return fmt . Errorf ( "invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt" , endpoint , err , endpoint . URL . Host , endpoint . URL . Host )
2014-10-10 23:22:12 -04:00
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP" , endpoint , err )
2014-08-26 19:21:04 -04:00
endpoint . URL . Scheme = "http"
2014-12-11 20:55:15 -05:00
var err2 error
if _ , err2 = endpoint . Ping ( ) ; err2 == nil {
2014-12-23 16:40:06 -05:00
return nil
2014-08-26 19:21:04 -04:00
}
2014-10-10 23:22:12 -04:00
2014-12-23 16:40:06 -05:00
return fmt . Errorf ( "invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v" , endpoint , err , err2 )
2014-08-26 19:21:04 -04:00
}
2014-12-23 16:40:06 -05:00
return nil
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
func newV1Endpoint ( address url . URL , tlsConfig * tls . Config , userAgent string , metaHeaders http . Header ) ( * V1Endpoint , error ) {
endpoint := & V1Endpoint {
2016-02-17 19:53:25 -05:00
IsSecure : ( tlsConfig == nil || ! tlsConfig . InsecureSkipVerify ) ,
URL : new ( url . URL ) ,
}
2014-12-11 20:55:15 -05:00
2016-02-17 19:53:25 -05:00
* endpoint . URL = address
// TODO(tiborvass): make sure a ConnectTimeout transport is used
tr := NewTransport ( tlsConfig )
endpoint . client = HTTPClient ( transport . NewTransport ( tr , DockerHeaders ( userAgent , metaHeaders ) ... ) )
return endpoint , nil
}
2016-03-01 02:07:41 -05:00
// trimV1Address trims the version off the address and returns the
// trimmed address or an error if there is a non-V1 version.
func trimV1Address ( address string ) ( string , error ) {
var (
chunks [ ] string
apiVersionStr string
)
if strings . HasSuffix ( address , "/" ) {
address = address [ : len ( address ) - 1 ]
}
chunks = strings . Split ( address , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
if apiVersionStr == "v1" {
return strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" ) , nil
}
for k , v := range apiVersions {
if k != APIVersion1 && apiVersionStr == v {
return "" , fmt . Errorf ( "unsupported V1 version path %s" , apiVersionStr )
}
}
return address , nil
}
func newV1EndpointFromStr ( address string , tlsConfig * tls . Config , userAgent string , metaHeaders http . Header ) ( * V1Endpoint , error ) {
2016-02-17 19:53:25 -05:00
if ! strings . HasPrefix ( address , "http://" ) && ! strings . HasPrefix ( address , "https://" ) {
2014-12-11 20:55:15 -05:00
address = "https://" + address
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
address , err := trimV1Address ( address )
if err != nil {
return nil , err
}
2015-02-12 13:23:22 -05:00
2016-03-01 02:07:41 -05:00
uri , err := url . Parse ( address )
2016-02-17 19:53:25 -05:00
if err != nil {
return nil , err
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
endpoint , err := newV1Endpoint ( * uri , tlsConfig , userAgent , metaHeaders )
2016-02-17 19:53:25 -05:00
if err != nil {
2014-10-03 15:46:42 -04:00
return nil , err
}
2015-02-12 13:23:22 -05:00
2014-12-11 20:55:15 -05:00
return endpoint , nil
2014-08-26 19:21:04 -04:00
}
2015-12-13 11:00:39 -05:00
// Get the formatted URL for the root of this registry Endpoint
2016-03-01 02:07:41 -05:00
func ( e * V1Endpoint ) String ( ) string {
return e . URL . String ( ) + "/v1/"
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
// Path returns a formatted string for the URL
// of this endpoint with the given path appended.
2016-03-01 02:07:41 -05:00
func ( e * V1Endpoint ) Path ( path string ) string {
return e . URL . String ( ) + "/v1/" + path
2014-12-11 20:55:15 -05:00
}
2016-03-01 02:07:41 -05:00
// Ping returns a PingResult which indicates whether the registry is standalone or not.
func ( e * V1Endpoint ) Ping ( ) ( PingResult , error ) {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "attempting v1 ping for registry endpoint %s" , e )
2014-12-11 20:55:15 -05:00
2015-07-21 15:40:36 -04:00
if e . String ( ) == IndexServer {
2014-12-11 20:55:15 -05:00
// Skip the check, we know this one is valid
2014-08-26 19:21:04 -04:00
// (and we never want to fallback to http in case of error)
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , nil
2014-08-26 19:21:04 -04:00
}
2015-05-14 10:12:54 -04:00
req , err := http . NewRequest ( "GET" , e . Path ( "_ping" ) , nil )
2014-08-26 19:21:04 -04:00
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , err
2014-08-26 19:21:04 -04:00
}
2015-05-15 21:35:04 -04:00
resp , err := e . client . Do ( req )
2014-08-26 19:21:04 -04:00
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , err
2014-08-26 19:21:04 -04:00
}
defer resp . Body . Close ( )
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , fmt . Errorf ( "error while reading the http response: %s" , err )
2014-08-26 19:21:04 -04:00
}
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
2015-07-21 15:40:36 -04:00
info := PingResult {
2014-08-26 19:21:04 -04:00
Standalone : true ,
}
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "Error unmarshalling the _ping PingResult: %s" , err )
2014-08-26 19:21:04 -04:00
// don't stop here. Just assume sane defaults
}
if hdr := resp . Header . Get ( "X-Docker-Registry-Version" ) ; hdr != "" {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Registry version header: '%s'" , hdr )
2014-08-26 19:21:04 -04:00
info . Version = hdr
}
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "PingResult.Version: %q" , info . Version )
2014-08-26 19:21:04 -04:00
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Registry standalone header: '%s'" , standalone )
2014-08-26 19:21:04 -04: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
}
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "PingResult.Standalone: %t" , info . Standalone )
2014-08-26 19:21:04 -04:00
return info , nil
}