2014-08-26 19:21:04 -04:00
package registry
import (
"encoding/json"
"fmt"
"io/ioutil"
2014-10-31 16:00:49 -04:00
"net"
2014-08-26 19:21:04 -04:00
"net/http"
"net/url"
"strings"
2014-10-24 13:12:35 -04:00
log "github.com/Sirupsen/logrus"
2014-12-19 17:44:18 -05:00
"github.com/docker/docker/registry/v2"
2014-08-26 19:21:04 -04:00
)
2014-11-11 16:31:15 -05:00
// for mocking in unit tests
var lookupIP = net . LookupIP
2014-12-11 20:55:15 -05:00
// scans string for api version in the URL path. returns the trimmed address, if version found, string and API version.
func scanForAPIVersion ( address string ) ( string , APIVersion ) {
2014-08-26 19:21:04 -04:00
var (
chunks [ ] string
apiVersionStr string
)
2014-12-11 20:55:15 -05:00
if strings . HasSuffix ( address , "/" ) {
address = address [ : len ( address ) - 1 ]
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
chunks = strings . Split ( address , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
2014-08-26 19:21:04 -04:00
for k , v := range apiVersions {
if apiVersionStr == v {
2014-12-11 20:55:15 -05:00
address = strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" )
return address , k
2014-08-26 19:21:04 -04:00
}
}
2014-12-11 20:55:15 -05:00
return address , APIVersionUnknown
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
// NewEndpoint parses the given address to return a registry endpoint.
2014-10-06 21:54:52 -04:00
func NewEndpoint ( index * IndexInfo ) ( * Endpoint , error ) {
// *TODO: Allow per-registry configuration of endpoints.
endpoint , err := newEndpoint ( index . GetAuthConfigKey ( ) , index . Secure )
2014-08-26 19:21:04 -04:00
if err != nil {
return nil , err
}
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
2014-12-23 16:40:06 -05:00
func validateEndpoint ( endpoint * Endpoint ) error {
2014-12-11 20:55:15 -05:00
log . Debugf ( "pinging registry endpoint %s" , endpoint )
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.
log . 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
func newEndpoint ( address string , secure bool ) ( * Endpoint , error ) {
2014-10-03 15:46:42 -04:00
var (
2014-12-11 20:55:15 -05:00
endpoint = new ( Endpoint )
trimmedAddress string
err error
2014-10-03 15:46:42 -04:00
)
2014-12-11 20:55:15 -05:00
if ! strings . HasPrefix ( address , "http" ) {
address = "https://" + address
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
trimmedAddress , endpoint . Version = scanForAPIVersion ( address )
if endpoint . URL , err = url . Parse ( trimmedAddress ) ; err != nil {
2014-10-03 15:46:42 -04:00
return nil , err
}
2014-12-11 20:55:15 -05:00
endpoint . IsSecure = secure
return endpoint , nil
2014-08-26 19:21:04 -04:00
}
2014-10-06 21:54:52 -04:00
func ( repoInfo * RepositoryInfo ) GetEndpoint ( ) ( * Endpoint , error ) {
return NewEndpoint ( repoInfo . Index )
}
2014-12-11 20:55:15 -05:00
// Endpoint stores basic information about a registry endpoint.
2014-08-26 19:21:04 -04:00
type Endpoint struct {
2014-12-11 20:55:15 -05:00
URL * url . URL
Version APIVersion
IsSecure bool
AuthChallenges [ ] * AuthorizationChallenge
2014-12-19 17:44:18 -05:00
URLBuilder * v2 . URLBuilder
2014-08-26 19:21:04 -04:00
}
// Get the formated URL for the root of this registry Endpoint
2014-12-11 20:55:15 -05:00
func ( e * Endpoint ) String ( ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL , e . Version )
}
// VersionString returns a formatted string of this
// endpoint address using the given API Version.
func ( e * Endpoint ) VersionString ( version APIVersion ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL , version )
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.
func ( e * Endpoint ) Path ( path string ) string {
return fmt . Sprintf ( "%s/v%d/%s" , e . URL , e . Version , path )
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
func ( e * Endpoint ) Ping ( ) ( RegistryInfo , error ) {
// The ping logic to use is determined by the registry endpoint version.
switch e . Version {
case APIVersion1 :
return e . pingV1 ( )
case APIVersion2 :
return e . pingV2 ( )
}
// APIVersionUnknown
// We should try v2 first...
e . Version = APIVersion2
regInfo , errV2 := e . pingV2 ( )
if errV2 == nil {
return regInfo , nil
}
// ... then fallback to v1.
e . Version = APIVersion1
regInfo , errV1 := e . pingV1 ( )
if errV1 == nil {
return regInfo , nil
}
e . Version = APIVersionUnknown
return RegistryInfo { } , fmt . Errorf ( "unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s" , e , errV2 , errV1 )
}
func ( e * Endpoint ) pingV1 ( ) ( RegistryInfo , error ) {
log . Debugf ( "attempting v1 ping for registry endpoint %s" , e )
2014-08-26 19:21:04 -04:00
if e . String ( ) == IndexServerAddress ( ) {
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)
return RegistryInfo { Standalone : false } , nil
}
2014-12-11 20:55:15 -05:00
req , err := http . NewRequest ( "GET" , e . Path ( "_ping" ) , nil )
2014-08-26 19:21:04 -04:00
if err != nil {
return RegistryInfo { Standalone : false } , err
}
2014-12-11 20:55:15 -05:00
resp , _ , err := doRequest ( req , nil , ConnectTimeout , e . IsSecure )
2014-08-26 19:21:04 -04:00
if err != nil {
return RegistryInfo { Standalone : false } , err
}
defer resp . Body . Close ( )
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2014-12-11 20:55:15 -05:00
return RegistryInfo { 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
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-02 20:41:57 -04:00
log . Debugf ( "RegistryInfo.Standalone: %t" , info . Standalone )
2014-08-26 19:21:04 -04:00
return info , nil
}
2014-12-11 20:55:15 -05:00
func ( e * Endpoint ) pingV2 ( ) ( RegistryInfo , error ) {
log . Debugf ( "attempting v2 ping for registry endpoint %s" , e )
req , err := http . NewRequest ( "GET" , e . Path ( "" ) , nil )
if err != nil {
return RegistryInfo { } , err
}
resp , _ , err := doRequest ( req , nil , ConnectTimeout , e . IsSecure )
if err != nil {
return RegistryInfo { } , err
}
defer resp . Body . Close ( )
2015-01-20 22:37:21 -05:00
// The endpoint may have multiple supported versions.
// Ensure it supports the v2 Registry API.
var supportsV2 bool
for _ , versionName := range resp . Header [ http . CanonicalHeaderKey ( "Docker-Distribution-API-Version" ) ] {
if versionName == "registry/2.0" {
supportsV2 = true
break
}
}
if ! supportsV2 {
return RegistryInfo { } , fmt . Errorf ( "%s does not appear to be a v2 registry endpoint" , e )
}
2014-12-11 20:55:15 -05:00
if resp . StatusCode == http . StatusOK {
// It would seem that no authentication/authorization is required.
// So we don't need to parse/add any authorization schemes.
return RegistryInfo { Standalone : true } , nil
}
if resp . StatusCode == http . StatusUnauthorized {
// Parse the WWW-Authenticate Header and store the challenges
// on this endpoint object.
e . AuthChallenges = parseAuthHeader ( resp . Header )
return RegistryInfo { } , nil
}
return RegistryInfo { } , fmt . Errorf ( "v2 registry endpoint returned status %d: %q" , resp . StatusCode , http . StatusText ( resp . StatusCode ) )
}