distribution/docs/endpoint.go
Don Kjer 64b000c3ea Deprecating ResolveRepositoryName
Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository

Moving --registry-mirror configuration to registry config

Created resolve_repository job

Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name.

Adding test for RepositoryInfo

Adding tests for opts.StringSetOpts and registry.ValidateMirror

Fixing search term use of repoInfo

Adding integration tests for registry mirror configuration

Normalizing LookupImage image name to match LocalName parsing rules

Normalizing repository LocalName to avoid multiple references to an official image

Removing errorOut use in tests

Removing TODO comment

gofmt changes

golint comments cleanup.  renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig

Splitting out builtins.Registry and registry.NewService calls

Stray whitespace cleanup

Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test

Factoring out ValidateRepositoryName from NewRepositoryInfo

Removing unused IndexServerURL

Allowing json marshaling of ServiceConfig.  Exposing ServiceConfig in /info

Switching to CamelCase for json marshaling

PR cleanup; removing 'Is' prefix from boolean members.  Removing unneeded json tags.

Removing non-cleanup related fix for 'localhost:[port]' in splitReposName

Merge fixes for gh9735

Fixing integration test

Reapplying #9754

Adding comment on config.IndexConfigs use from isSecureIndex

Remove unused error return value from isSecureIndex

Signed-off-by: Don Kjer <don.kjer@gmail.com>

Adding back comment in isSecureIndex

Signed-off-by: Don Kjer <don.kjer@gmail.com>
2015-01-08 20:14:58 +00:00

208 lines
6.7 KiB
Go

package registry
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
log "github.com/Sirupsen/logrus"
)
// for mocking in unit tests
var lookupIP = net.LookupIP
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
func scanForAPIVersion(hostname string) (string, APIVersion) {
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
}
func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints.
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
if err != nil {
return nil, err
}
// Try HTTPS ping to registry
endpoint.URL.Scheme = "https"
if _, err := endpoint.Ping(); err != nil {
//TODO: triggering highland build can be done there without "failing"
if index.Secure {
// 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)
endpoint.URL.Scheme = "http"
_, err2 := endpoint.Ping()
if err2 == nil {
return endpoint, nil
}
return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2)
}
return endpoint, nil
}
func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var (
endpoint = Endpoint{}
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
}
endpoint.secure = secure
return &endpoint, nil
}
func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
return NewEndpoint(repoInfo.Index)
}
type Endpoint struct {
URL *url.URL
Version APIVersion
secure bool
}
// 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
}
resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
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
}
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
return info, nil
}
// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
//
// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
// insecure.
//
// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
// of insecureRegistries.
func (config *ServiceConfig) isSecureIndex(indexName string) bool {
// Check for configured index, first. This is needed in case isSecureIndex
// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
if index, ok := config.IndexConfigs[indexName]; ok {
return index.Secure
}
host, _, err := net.SplitHostPort(indexName)
if err != nil {
// assume indexName is of the form `host` without the port and go on.
host = indexName
}
addrs, err := lookupIP(host)
if err != nil {
ip := net.ParseIP(host)
if ip != nil {
addrs = []net.IP{ip}
}
// if ip == nil, then `host` is neither an IP nor it could be looked up,
// either because the index is unreachable, or because the index is behind an HTTP proxy.
// So, len(addrs) == 0 and we're not aborting.
}
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
for _, addr := range addrs {
for _, ipnet := range config.InsecureRegistryCIDRs {
// check if the addr falls in the subnet
if (*net.IPNet)(ipnet).Contains(addr) {
return false
}
}
}
return true
}