2014-08-27 01:21:04 +02:00
package registry
import (
"encoding/json"
"fmt"
"io/ioutil"
2014-10-31 21:00:49 +01:00
"net"
2014-08-27 01:21:04 +02:00
"net/http"
"net/url"
"strings"
2014-10-24 19:12:35 +02:00
log "github.com/Sirupsen/logrus"
2014-08-27 01:21:04 +02:00
)
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
2014-10-06 21:34:39 +02:00
func scanForAPIVersion ( hostname string ) ( string , APIVersion ) {
2014-08-27 01:21:04 +02:00
var (
chunks [ ] string
apiVersionStr string
)
if strings . HasSuffix ( hostname , "/" ) {
chunks = strings . Split ( hostname [ : len ( hostname ) - 1 ] , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
} else {
chunks = strings . Split ( hostname , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
}
for k , v := range apiVersions {
if apiVersionStr == v {
hostname = strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" )
return hostname , k
}
}
return hostname , DefaultAPIVersion
}
2014-11-11 23:37:44 +01:00
func NewEndpoint ( hostname string , insecureRegistries [ ] string ) ( * Endpoint , error ) {
2014-11-13 15:56:36 +01:00
endpoint , err := newEndpoint ( hostname , insecureRegistries )
2014-08-27 01:21:04 +02:00
if err != nil {
return nil , err
}
2014-10-11 05:22:12 +02:00
// Try HTTPS ping to registry
2014-08-27 01:21:04 +02:00
endpoint . URL . Scheme = "https"
if _ , err := endpoint . Ping ( ) ; err != nil {
2014-10-11 05:22:12 +02:00
//TODO: triggering highland build can be done there without "failing"
2014-11-13 15:56:36 +01:00
if endpoint . secure {
2014-10-11 05:22:12 +02: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.
return nil , 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 )
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
log . Debugf ( "Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP" , endpoint , err )
2014-08-27 01:21:04 +02:00
endpoint . URL . Scheme = "http"
2014-10-11 05:22:12 +02:00
_ , err2 := endpoint . Ping ( )
if err2 == nil {
return endpoint , nil
2014-08-27 01:21:04 +02:00
}
2014-10-11 05:22:12 +02:00
return nil , fmt . Errorf ( "Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v" , endpoint , err , err2 )
2014-08-27 01:21:04 +02:00
}
2014-10-03 21:46:42 +02:00
return endpoint , nil
}
2014-11-13 15:56:36 +01:00
func newEndpoint ( hostname string , insecureRegistries [ ] string ) ( * Endpoint , error ) {
2014-10-03 21:46:42 +02:00
var (
2014-11-13 15:56:36 +01:00
endpoint = Endpoint { }
2014-10-03 21:46:42 +02:00
trimmedHostname string
err error
)
if ! strings . HasPrefix ( hostname , "http" ) {
hostname = "https://" + hostname
}
trimmedHostname , endpoint . Version = scanForAPIVersion ( hostname )
endpoint . URL , err = url . Parse ( trimmedHostname )
if err != nil {
return nil , err
}
2014-11-13 15:56:36 +01:00
endpoint . secure = isSecure ( endpoint . URL . Host , insecureRegistries )
2014-08-27 01:21:04 +02:00
return & endpoint , nil
}
type Endpoint struct {
URL * url . URL
Version APIVersion
2014-10-11 05:22:12 +02:00
secure bool
2014-08-27 01:21:04 +02:00
}
// Get the formated URL for the root of this registry Endpoint
func ( e Endpoint ) String ( ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL . String ( ) , e . Version )
}
func ( e Endpoint ) VersionString ( version APIVersion ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL . String ( ) , version )
}
func ( e Endpoint ) Ping ( ) ( RegistryInfo , error ) {
if e . String ( ) == IndexServerAddress ( ) {
// Skip the check, we now this one is valid
// (and we never want to fallback to http in case of error)
return RegistryInfo { Standalone : false } , nil
}
req , err := http . NewRequest ( "GET" , e . String ( ) + "_ping" , nil )
if err != nil {
return RegistryInfo { Standalone : false } , err
}
2014-10-11 05:22:12 +02:00
resp , _ , err := doRequest ( req , nil , ConnectTimeout , e . secure )
2014-08-27 01:21:04 +02:00
if err != nil {
return RegistryInfo { Standalone : false } , err
}
defer resp . Body . Close ( )
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return RegistryInfo { Standalone : false } , fmt . Errorf ( "Error while reading the http response: %s" , err )
}
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
info := RegistryInfo {
Standalone : true ,
}
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
log . Debugf ( "Error unmarshalling the _ping RegistryInfo: %s" , err )
// don't stop here. Just assume sane defaults
}
if hdr := resp . Header . Get ( "X-Docker-Registry-Version" ) ; hdr != "" {
log . Debugf ( "Registry version header: '%s'" , hdr )
info . Version = hdr
}
log . Debugf ( "RegistryInfo.Version: %q" , info . Version )
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
log . Debugf ( "Registry standalone header: '%s'" , standalone )
// 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
}
2014-10-03 02:41:57 +02:00
log . Debugf ( "RegistryInfo.Standalone: %t" , info . Standalone )
2014-08-27 01:21:04 +02:00
return info , nil
}
2014-10-11 05:22:12 +02:00
2014-11-11 23:37:44 +01:00
// isSecure returns false if the provided hostname is part of the list of insecure registries.
2014-10-11 05:22:12 +02:00
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
2014-11-11 23:37:44 +01:00
func isSecure ( hostname string , insecureRegistries [ ] string ) bool {
2014-11-13 15:56:36 +01:00
if hostname == IndexServerURL . Host {
2014-10-11 05:22:12 +02:00
return true
}
2014-11-12 18:08:45 +01:00
host , _ , err := net . SplitHostPort ( hostname )
if err != nil {
host = hostname
}
if host == "127.0.0.1" || host == "localhost" {
return false
}
2014-10-31 21:00:49 +01:00
if len ( insecureRegistries ) == 0 {
return true
}
2014-11-12 18:08:45 +01:00
2014-10-11 05:22:12 +02:00
for _ , h := range insecureRegistries {
if hostname == h {
return false
}
}
return true
}