Moving NewIndexInfo, NewRepositoryInfo and associated helpers into config.go

Signed-off-by: Don Kjer <don.kjer@gmail.com>
This commit is contained in:
Don Kjer 2015-01-07 23:42:01 +00:00
Родитель 568f86eb18
Коммит 6f0068f273
6 изменённых файлов: 290 добавлений и 282 удалений

Просмотреть файл

@ -17,13 +17,6 @@ import (
const ( const (
// Where we store the config file // Where we store the config file
CONFIGFILE = ".dockercfg" CONFIGFILE = ".dockercfg"
// Only used for user auth + account creation
INDEXSERVER = "https://index.docker.io/v1/"
REGISTRYSERVER = "https://registry-1.docker.io/v1/"
INDEXNAME = "docker.io"
// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
) )
var ( var (
@ -43,14 +36,6 @@ type ConfigFile struct {
rootPath string rootPath string
} }
func IndexServerAddress() string {
return INDEXSERVER
}
func IndexServerName() string {
return INDEXNAME
}
// create a base64 encoded auth string to store in config // create a base64 encoded auth string to store in config
func encodeAuth(authConfig *AuthConfig) string { func encodeAuth(authConfig *AuthConfig) string {
authStr := authConfig.Username + ":" + authConfig.Password authStr := authConfig.Username + ":" + authConfig.Password

Просмотреть файл

@ -2,12 +2,16 @@ package registry
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"regexp"
"strings"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/utils"
) )
// Options holds command line options. // Options holds command line options.
@ -16,6 +20,30 @@ type Options struct {
InsecureRegistries opts.ListOpts InsecureRegistries opts.ListOpts
} }
const (
// Only used for user auth + account creation
INDEXSERVER = "https://index.docker.io/v1/"
REGISTRYSERVER = "https://registry-1.docker.io/v1/"
INDEXNAME = "docker.io"
// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
)
var (
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
emptyServiceConfig = NewServiceConfig(nil)
validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`)
validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
)
func IndexServerAddress() string {
return INDEXSERVER
}
func IndexServerName() string {
return INDEXNAME
}
// InstallFlags adds command-line options to the top-level flag parser for // InstallFlags adds command-line options to the top-level flag parser for
// the current process. // the current process.
func (options *Options) InstallFlags() { func (options *Options) InstallFlags() {
@ -25,34 +53,6 @@ func (options *Options) InstallFlags() {
flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)") flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
} }
// ValidateMirror validates an HTTP(S) registry mirror
func ValidateMirror(val string) (string, error) {
uri, err := url.Parse(val)
if err != nil {
return "", fmt.Errorf("%s is not a valid URI", val)
}
if uri.Scheme != "http" && uri.Scheme != "https" {
return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
}
if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
}
return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
}
// ValidateIndexName validates an index name.
func ValidateIndexName(val string) (string, error) {
// 'index.docker.io' => 'docker.io'
if val == "index."+IndexServerName() {
val = IndexServerName()
}
// *TODO: Check if valid hostname[:port]/ip[:port]?
return val, nil
}
type netIPNet net.IPNet type netIPNet net.IPNet
func (ipnet *netIPNet) MarshalJSON() ([]byte, error) { func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
@ -124,3 +124,259 @@ func NewServiceConfig(options *Options) *ServiceConfig {
return config return config
} }
// 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
}
// ValidateMirror validates an HTTP(S) registry mirror
func ValidateMirror(val string) (string, error) {
uri, err := url.Parse(val)
if err != nil {
return "", fmt.Errorf("%s is not a valid URI", val)
}
if uri.Scheme != "http" && uri.Scheme != "https" {
return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
}
if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
}
return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
}
// ValidateIndexName validates an index name.
func ValidateIndexName(val string) (string, error) {
// 'index.docker.io' => 'docker.io'
if val == "index."+IndexServerName() {
val = IndexServerName()
}
// *TODO: Check if valid hostname[:port]/ip[:port]?
return val, nil
}
func validateRemoteName(remoteName string) error {
var (
namespace string
name string
)
nameParts := strings.SplitN(remoteName, "/", 2)
if len(nameParts) < 2 {
namespace = "library"
name = nameParts[0]
// the repository name must not be a valid image ID
if err := utils.ValidateID(name); err == nil {
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
}
} else {
namespace = nameParts[0]
name = nameParts[1]
}
if !validNamespaceChars.MatchString(namespace) {
return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
}
if len(namespace) < 4 || len(namespace) > 30 {
return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
}
if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
}
if strings.Contains(namespace, "--") {
return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
}
if !validRepo.MatchString(name) {
return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
}
return nil
}
func validateNoSchema(reposName string) error {
if strings.Contains(reposName, "://") {
// It cannot contain a scheme!
return ErrInvalidRepositoryName
}
return nil
}
// ValidateRepositoryName validates a repository name
func ValidateRepositoryName(reposName string) error {
var err error
if err = validateNoSchema(reposName); err != nil {
return err
}
indexName, remoteName := splitReposName(reposName)
if _, err = ValidateIndexName(indexName); err != nil {
return err
}
return validateRemoteName(remoteName)
}
// NewIndexInfo returns IndexInfo configuration from indexName
func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
var err error
indexName, err = ValidateIndexName(indexName)
if err != nil {
return nil, err
}
// Return any configured index info, first.
if index, ok := config.IndexConfigs[indexName]; ok {
return index, nil
}
// Construct a non-configured index info.
index := &IndexInfo{
Name: indexName,
Mirrors: make([]string, 0),
Official: false,
}
index.Secure = config.isSecureIndex(indexName)
return index, nil
}
// GetAuthConfigKey special-cases using the full index address of the official
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
func (index *IndexInfo) GetAuthConfigKey() string {
if index.Official {
return IndexServerAddress()
}
return index.Name
}
// splitReposName breaks a reposName into an index name and remote name
func splitReposName(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = IndexServerName()
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
}
return indexName, remoteName
}
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInfo, error) {
if err := validateNoSchema(reposName); err != nil {
return nil, err
}
indexName, remoteName := splitReposName(reposName)
if err := validateRemoteName(remoteName); err != nil {
return nil, err
}
repoInfo := &RepositoryInfo{
RemoteName: remoteName,
}
var err error
repoInfo.Index, err = config.NewIndexInfo(indexName)
if err != nil {
return nil, err
}
if repoInfo.Index.Official {
normalizedName := repoInfo.RemoteName
if strings.HasPrefix(normalizedName, "library/") {
// If pull "library/foo", it's stored locally under "foo"
normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
}
repoInfo.LocalName = normalizedName
repoInfo.RemoteName = normalizedName
// If the normalized name does not contain a '/' (e.g. "foo")
// then it is an official repo.
if strings.IndexRune(normalizedName, '/') == -1 {
repoInfo.Official = true
// Fix up remote name for official repos.
repoInfo.RemoteName = "library/" + normalizedName
}
// *TODO: Prefix this with 'docker.io/'.
repoInfo.CanonicalName = repoInfo.LocalName
} else {
// *TODO: Decouple index name from hostname (via registry configuration?)
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
repoInfo.CanonicalName = repoInfo.LocalName
}
return repoInfo, nil
}
// GetSearchTerm special-cases using local name for official index, and
// remote name for private indexes.
func (repoInfo *RepositoryInfo) GetSearchTerm() string {
if repoInfo.Index.Official {
return repoInfo.LocalName
}
return repoInfo.RemoteName
}
// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
// lacks registry configuration.
func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
return emptyServiceConfig.NewRepositoryInfo(reposName)
}
// NormalizeLocalName transforms a repository name into a normalize LocalName
// Passes through the name without transformation on error (image id, etc)
func NormalizeLocalName(name string) string {
repoInfo, err := ParseRepositoryInfo(name)
if err != nil {
return name
}
return repoInfo.LocalName
}

Просмотреть файл

@ -157,52 +157,3 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
return info, nil 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
}

Просмотреть файл

@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"strings" "strings"
"time" "time"
@ -19,13 +18,9 @@ import (
) )
var ( var (
ErrAlreadyExists = errors.New("Image already exists") ErrAlreadyExists = errors.New("Image already exists")
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") ErrDoesNotExist = errors.New("Image does not exist")
ErrDoesNotExist = errors.New("Image does not exist") errLoginRequired = errors.New("Authentication is required.")
errLoginRequired = errors.New("Authentication is required.")
validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`)
validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
emptyServiceConfig = NewServiceConfig(nil)
) )
type TimeoutType uint32 type TimeoutType uint32
@ -161,185 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
return res, client, err return res, client, err
} }
func validateRemoteName(remoteName string) error {
var (
namespace string
name string
)
nameParts := strings.SplitN(remoteName, "/", 2)
if len(nameParts) < 2 {
namespace = "library"
name = nameParts[0]
// the repository name must not be a valid image ID
if err := utils.ValidateID(name); err == nil {
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
}
} else {
namespace = nameParts[0]
name = nameParts[1]
}
if !validNamespaceChars.MatchString(namespace) {
return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
}
if len(namespace) < 4 || len(namespace) > 30 {
return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
}
if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
}
if strings.Contains(namespace, "--") {
return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
}
if !validRepo.MatchString(name) {
return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
}
return nil
}
// NewIndexInfo returns IndexInfo configuration from indexName
func NewIndexInfo(config *ServiceConfig, indexName string) (*IndexInfo, error) {
var err error
indexName, err = ValidateIndexName(indexName)
if err != nil {
return nil, err
}
// Return any configured index info, first.
if index, ok := config.IndexConfigs[indexName]; ok {
return index, nil
}
// Construct a non-configured index info.
index := &IndexInfo{
Name: indexName,
Mirrors: make([]string, 0),
Official: false,
}
index.Secure = config.isSecureIndex(indexName)
return index, nil
}
func validateNoSchema(reposName string) error {
if strings.Contains(reposName, "://") {
// It cannot contain a scheme!
return ErrInvalidRepositoryName
}
return nil
}
// splitReposName breaks a reposName into an index name and remote name
func splitReposName(reposName string) (string, string) {
nameParts := strings.SplitN(reposName, "/", 2)
var indexName, remoteName string
if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
// 'docker.io'
indexName = IndexServerName()
remoteName = reposName
} else {
indexName = nameParts[0]
remoteName = nameParts[1]
}
return indexName, remoteName
}
// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
func NewRepositoryInfo(config *ServiceConfig, reposName string) (*RepositoryInfo, error) {
if err := validateNoSchema(reposName); err != nil {
return nil, err
}
indexName, remoteName := splitReposName(reposName)
if err := validateRemoteName(remoteName); err != nil {
return nil, err
}
repoInfo := &RepositoryInfo{
RemoteName: remoteName,
}
var err error
repoInfo.Index, err = NewIndexInfo(config, indexName)
if err != nil {
return nil, err
}
if repoInfo.Index.Official {
normalizedName := repoInfo.RemoteName
if strings.HasPrefix(normalizedName, "library/") {
// If pull "library/foo", it's stored locally under "foo"
normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
}
repoInfo.LocalName = normalizedName
repoInfo.RemoteName = normalizedName
// If the normalized name does not contain a '/' (e.g. "foo")
// then it is an official repo.
if strings.IndexRune(normalizedName, '/') == -1 {
repoInfo.Official = true
// Fix up remote name for official repos.
repoInfo.RemoteName = "library/" + normalizedName
}
// *TODO: Prefix this with 'docker.io/'.
repoInfo.CanonicalName = repoInfo.LocalName
} else {
// *TODO: Decouple index name from hostname (via registry configuration?)
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
repoInfo.CanonicalName = repoInfo.LocalName
}
return repoInfo, nil
}
// ValidateRepositoryName validates a repository name
func ValidateRepositoryName(reposName string) error {
var err error
if err = validateNoSchema(reposName); err != nil {
return err
}
indexName, remoteName := splitReposName(reposName)
if _, err = ValidateIndexName(indexName); err != nil {
return err
}
return validateRemoteName(remoteName)
}
// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
// lacks registry configuration.
func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
return NewRepositoryInfo(emptyServiceConfig, reposName)
}
// NormalizeLocalName transforms a repository name into a normalize LocalName
// Passes through the name without transformation on error (image id, etc)
func NormalizeLocalName(name string) string {
repoInfo, err := ParseRepositoryInfo(name)
if err != nil {
return name
}
return repoInfo.LocalName
}
// GetAuthConfigKey special-cases using the full index address of the official
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
func (index *IndexInfo) GetAuthConfigKey() string {
if index.Official {
return IndexServerAddress()
}
return index.Name
}
// GetSearchTerm special-cases using local name for official index, and
// remote name for private indexes.
func (repoInfo *RepositoryInfo) GetSearchTerm() string {
if repoInfo.Index.Official {
return repoInfo.LocalName
}
return repoInfo.RemoteName
}
func trustedLocation(req *http.Request) bool { func trustedLocation(req *http.Request) bool {
var ( var (
trusteds = []string{"docker.com", "docker.io"} trusteds = []string{"docker.com", "docker.io"}

Просмотреть файл

@ -561,7 +561,7 @@ func TestParseRepositoryInfo(t *testing.T) {
func TestNewIndexInfo(t *testing.T) { func TestNewIndexInfo(t *testing.T) {
testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) { testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
for indexName, expectedIndexInfo := range expectedIndexInfos { for indexName, expectedIndexInfo := range expectedIndexInfos {
index, err := NewIndexInfo(config, indexName) index, err := config.NewIndexInfo(indexName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} else { } else {

Просмотреть файл

@ -130,7 +130,7 @@ func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
reposName = job.Args[0] reposName = job.Args[0]
) )
repoInfo, err := NewRepositoryInfo(s.Config, reposName) repoInfo, err := s.Config.NewRepositoryInfo(reposName)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
@ -168,7 +168,7 @@ func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
indexName = job.Args[0] indexName = job.Args[0]
) )
index, err := NewIndexInfo(s.Config, indexName) index, err := s.Config.NewIndexInfo(indexName)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }