Merge pull request #8456 from lindenlab/cleanup-repository-info

Cleanup: Replace ResolveRepositoryName with RepositoryInfo{}
This commit is contained in:
Tibor Vass 2015-01-08 17:19:03 -05:00
commit 6870bde584
31 changed files with 1607 additions and 471 deletions

View File

@ -222,7 +222,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
//Check if the given image name can be resolved //Check if the given image name can be resolved
if *tag != "" { if *tag != "" {
repository, tag := parsers.ParseRepositoryTag(*tag) repository, tag := parsers.ParseRepositoryTag(*tag)
if _, _, err := registry.ResolveRepositoryName(repository); err != nil { if err := registry.ValidateRepositoryName(repository); err != nil {
return err return err
} }
if len(tag) > 0 { if len(tag) > 0 {
@ -1148,7 +1148,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
if repository != "" { if repository != "" {
//Check if the given image name can be resolved //Check if the given image name can be resolved
repo, _ := parsers.ParseRepositoryTag(repository) repo, _ := parsers.ParseRepositoryTag(repository)
if _, _, err := registry.ResolveRepositoryName(repo); err != nil { if err := registry.ValidateRepositoryName(repo); err != nil {
return err return err
} }
} }
@ -1174,23 +1174,23 @@ func (cli *DockerCli) CmdPush(args ...string) error {
remote, tag := parsers.ParseRepositoryTag(name) remote, tag := parsers.ParseRepositoryTag(name)
// Resolve the Repository name from fqn to hostname + name // Resolve the Repository name from fqn to RepositoryInfo
hostname, _, err := registry.ResolveRepositoryName(remote) repoInfo, err := registry.ParseRepositoryInfo(remote)
if err != nil { if err != nil {
return err return err
} }
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname) authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
// If we're not using a custom registry, we know the restrictions // If we're not using a custom registry, we know the restrictions
// applied to repository names and can warn the user in advance. // applied to repository names and can warn the user in advance.
// Custom repositories can have different rules, and we must also // Custom repositories can have different rules, and we must also
// allow pushing by image ID. // allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 { if repoInfo.Official {
username := cli.configFile.Configs[registry.IndexServerAddress()].Username username := authConfig.Username
if username == "" { if username == "" {
username = "<user>" username = "<user>"
} }
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name) return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
} }
v := url.Values{} v := url.Values{}
@ -1212,10 +1212,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
if err := push(authConfig); err != nil { if err := push(authConfig); err != nil {
if strings.Contains(err.Error(), "Status 401") { if strings.Contains(err.Error(), "Status 401") {
fmt.Fprintln(cli.out, "\nPlease login prior to push:") fmt.Fprintln(cli.out, "\nPlease login prior to push:")
if err := cli.CmdLogin(hostname); err != nil { if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
return err return err
} }
authConfig := cli.configFile.ResolveAuthConfig(hostname) authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
return push(authConfig) return push(authConfig)
} }
return err return err
@ -1245,8 +1245,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
v.Set("fromImage", newRemote) v.Set("fromImage", newRemote)
// Resolve the Repository name from fqn to hostname + name // Resolve the Repository name from fqn to RepositoryInfo
hostname, _, err := registry.ResolveRepositoryName(taglessRemote) repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
if err != nil { if err != nil {
return err return err
} }
@ -1254,7 +1254,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
cli.LoadConfigFile() cli.LoadConfigFile()
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname) authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
pull := func(authConfig registry.AuthConfig) error { pull := func(authConfig registry.AuthConfig) error {
buf, err := json.Marshal(authConfig) buf, err := json.Marshal(authConfig)
@ -1273,10 +1273,10 @@ func (cli *DockerCli) CmdPull(args ...string) error {
if err := pull(authConfig); err != nil { if err := pull(authConfig); err != nil {
if strings.Contains(err.Error(), "Status 401") { if strings.Contains(err.Error(), "Status 401") {
fmt.Fprintln(cli.out, "\nPlease login prior to pull:") fmt.Fprintln(cli.out, "\nPlease login prior to pull:")
if err := cli.CmdLogin(hostname); err != nil { if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
return err return err
} }
authConfig := cli.configFile.ResolveAuthConfig(hostname) authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
return pull(authConfig) return pull(authConfig)
} }
return err return err
@ -1691,7 +1691,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
//Check if the given image name can be resolved //Check if the given image name can be resolved
if repository != "" { if repository != "" {
if _, _, err := registry.ResolveRepositoryName(repository); err != nil { if err := registry.ValidateRepositoryName(repository); err != nil {
return err return err
} }
} }
@ -2002,7 +2002,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
) )
//Check if the given image name can be resolved //Check if the given image name can be resolved
if _, _, err := registry.ResolveRepositoryName(repository); err != nil { if err := registry.ValidateRepositoryName(repository); err != nil {
return err return err
} }
v.Set("repo", repository) v.Set("repo", repository)
@ -2032,8 +2032,8 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
v.Set("fromImage", repos) v.Set("fromImage", repos)
v.Set("tag", tag) v.Set("tag", tag)
// Resolve the Repository name from fqn to hostname + name // Resolve the Repository name from fqn to RepositoryInfo
hostname, _, err := registry.ResolveRepositoryName(repos) repoInfo, err := registry.ParseRepositoryInfo(repos)
if err != nil { if err != nil {
return err return err
} }
@ -2042,7 +2042,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
cli.LoadConfigFile() cli.LoadConfigFile()
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname) authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
buf, err := json.Marshal(authConfig) buf, err := json.Marshal(authConfig)
if err != nil { if err != nil {
return err return err

View File

@ -66,7 +66,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
if passAuthInfo { if passAuthInfo {
cli.LoadConfigFile() cli.LoadConfigFile()
// Resolve the Auth config relevant for this server // Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) authConfig := cli.configFile.Configs[registry.IndexServerAddress()]
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
buf, err := json.Marshal(authConfig) buf, err := json.Marshal(authConfig)
if err != nil { if err != nil {
@ -260,7 +260,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
sigchan := make(chan os.Signal, 1) sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH) gosignal.Notify(sigchan, signal.SIGWINCH)
go func() { go func() {
for _ = range sigchan { // This tmp := range..., _ = tmp workaround is needed to
// suppress gofmt warnings while still preserve go1.3 compatibility
for tmp := range sigchan {
_ = tmp
cli.resizeTty(id, isExec) cli.resizeTty(id, isExec)
} }
}() }()

View File

@ -427,17 +427,17 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
if tag == "" { if tag == "" {
tag = "latest" tag = "latest"
} }
job := b.Engine.Job("pull", remote, tag)
pullRegistryAuth := b.AuthConfig pullRegistryAuth := b.AuthConfig
if len(b.AuthConfigFile.Configs) > 0 { if len(b.AuthConfigFile.Configs) > 0 {
// The request came with a full auth config file, we prefer to use that // The request came with a full auth config file, we prefer to use that
endpoint, _, err := registry.ResolveRepositoryName(remote) repoInfo, err := registry.ResolveRepositoryInfo(job, remote)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint) resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index)
pullRegistryAuth = &resolvedAuth pullRegistryAuth = &resolvedAuth
} }
job := b.Engine.Job("pull", remote, tag)
job.SetenvBool("json", b.StreamFormatter.Json()) job.SetenvBool("json", b.StreamFormatter.Json())
job.SetenvBool("parallel", true) job.SetenvBool("parallel", true)
job.SetenvJson("authConfig", pullRegistryAuth) job.SetenvJson("authConfig", pullRegistryAuth)

View File

@ -50,7 +50,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
repoName, tag = parsers.ParseRepositoryTag(repoName) repoName, tag = parsers.ParseRepositoryTag(repoName)
if repoName != "" { if repoName != "" {
if _, _, err := registry.ResolveRepositoryName(repoName); err != nil { if err := registry.ValidateRepositoryName(repoName); err != nil {
return job.Error(err) return job.Error(err)
} }
if len(tag) > 0 { if len(tag) > 0 {

View File

@ -23,7 +23,6 @@ type Config struct {
AutoRestart bool AutoRestart bool
Dns []string Dns []string
DnsSearch []string DnsSearch []string
Mirrors []string
EnableIptables bool EnableIptables bool
EnableIpForward bool EnableIpForward bool
EnableIpMasq bool EnableIpMasq bool
@ -31,7 +30,6 @@ type Config struct {
BridgeIface string BridgeIface string
BridgeIP string BridgeIP string
FixedCIDR string FixedCIDR string
InsecureRegistries []string
InterContainerCommunication bool InterContainerCommunication bool
GraphDriver string GraphDriver string
GraphOptions []string GraphOptions []string
@ -58,7 +56,6 @@ func (config *Config) InstallFlags() {
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b") flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking") flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)") flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
opts.ListVar(&config.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.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication") flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver") flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
@ -69,16 +66,7 @@ func (config *Config) InstallFlags() {
// FIXME: why the inconsistency between "hosts" and "sockets"? // FIXME: why the inconsistency between "hosts" and "sockets"?
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers") opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)") opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)")
// Localhost is by default considered as an insecure registry
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
//
// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
// daemon flags on boot2docker?
// If so, do not forget to check the TODO in TestIsSecure
config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8")
} }
func getDefaultNetworkMtu() int { func getDefaultNetworkMtu() int {

View File

@ -898,7 +898,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
} }
log.Debugf("Creating repository list") log.Debugf("Creating repository list")
repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors, config.InsecureRegistries) repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
if err != nil { if err != nil {
return nil, fmt.Errorf("Couldn't create Tag store: %s", err) return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
} }

View File

@ -55,6 +55,15 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
if err := cjob.Run(); err != nil { if err := cjob.Run(); err != nil {
return job.Error(err) return job.Error(err)
} }
registryJob := job.Eng.Job("registry_config")
registryEnv, _ := registryJob.Stdout.AddEnv()
if err := registryJob.Run(); err != nil {
return job.Error(err)
}
registryConfig := registry.ServiceConfig{}
if err := registryEnv.GetJson("config", &registryConfig); err != nil {
return job.Error(err)
}
v := &engine.Env{} v := &engine.Env{}
v.SetJson("ID", daemon.ID) v.SetJson("ID", daemon.ID)
v.SetInt("Containers", len(daemon.List())) v.SetInt("Containers", len(daemon.List()))
@ -72,6 +81,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
v.Set("KernelVersion", kernelVersion) v.Set("KernelVersion", kernelVersion)
v.Set("OperatingSystem", operatingSystem) v.Set("OperatingSystem", operatingSystem)
v.Set("IndexServerAddress", registry.IndexServerAddress()) v.Set("IndexServerAddress", registry.IndexServerAddress())
v.SetJson("RegistryConfig", registryConfig)
v.Set("InitSha1", dockerversion.INITSHA1) v.Set("InitSha1", dockerversion.INITSHA1)
v.Set("InitPath", initPath) v.Set("InitPath", initPath)
v.SetInt("NCPU", runtime.NumCPU()) v.SetInt("NCPU", runtime.NumCPU())

View File

@ -20,10 +20,12 @@ const CanDaemon = true
var ( var (
daemonCfg = &daemon.Config{} daemonCfg = &daemon.Config{}
registryCfg = &registry.Options{}
) )
func init() { func init() {
daemonCfg.InstallFlags() daemonCfg.InstallFlags()
registryCfg.InstallFlags()
} }
func mainDaemon() { func mainDaemon() {
@ -42,7 +44,7 @@ func mainDaemon() {
} }
// load registry service // load registry service
if err := registry.NewService(daemonCfg.InsecureRegistries).Install(eng); err != nil { if err := registry.NewService(registryCfg).Install(eng); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/engine" "github.com/docker/docker/engine"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
) )
// CmdImageExport exports all images with the given tag. All versions // CmdImageExport exports all images with the given tag. All versions
@ -39,6 +40,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
} }
} }
for _, name := range job.Args { for _, name := range job.Args {
name = registry.NormalizeLocalName(name)
log.Debugf("Serializing %s", name) log.Debugf("Serializing %s", name)
rootRepo := s.Repositories[name] rootRepo := s.Repositories[name]
if rootRepo != nil { if rootRepo != nil {

View File

@ -85,9 +85,14 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
sf = utils.NewStreamFormatter(job.GetenvBool("json")) sf = utils.NewStreamFormatter(job.GetenvBool("json"))
authConfig = &registry.AuthConfig{} authConfig = &registry.AuthConfig{}
metaHeaders map[string][]string metaHeaders map[string][]string
mirrors []string
) )
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
if err != nil {
return job.Error(err)
}
if len(job.Args) > 1 { if len(job.Args) > 1 {
tag = job.Args[1] tag = job.Args[1]
} }
@ -95,25 +100,19 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig) job.GetenvJson("authConfig", authConfig)
job.GetenvJson("metaHeaders", &metaHeaders) job.GetenvJson("metaHeaders", &metaHeaders)
c, err := s.poolAdd("pull", localName+":"+tag) c, err := s.poolAdd("pull", repoInfo.LocalName+":"+tag)
if err != nil { if err != nil {
if c != nil { if c != nil {
// Another pull of the same repository is already taking place; just wait for it to finish // Another pull of the same repository is already taking place; just wait for it to finish
job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName)) job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName))
<-c <-c
return engine.StatusOK return engine.StatusOK
} }
return job.Error(err) return job.Error(err)
} }
defer s.poolRemove("pull", localName+":"+tag) defer s.poolRemove("pull", repoInfo.LocalName+":"+tag)
// Resolve the Repository name from fqn to endpoint + name endpoint, err := repoInfo.GetEndpoint()
hostname, remoteName, err := registry.ResolveRepositoryName(localName)
if err != nil {
return job.Error(err)
}
endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
@ -123,32 +122,18 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
return job.Error(err) return job.Error(err)
} }
var isOfficial bool logName := repoInfo.LocalName
if endpoint.VersionString(1) == registry.IndexServerAddress() {
// If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar"
localName = remoteName
isOfficial = isOfficialName(remoteName)
if isOfficial && strings.IndexRune(remoteName, '/') == -1 {
remoteName = "library/" + remoteName
}
// Use provided mirrors, if any
mirrors = s.mirrors
}
logName := localName
if tag != "" { if tag != "" {
logName += ":" + tag logName += ":" + tag
} }
if len(mirrors) == 0 && (isOfficial || endpoint.Version == registry.APIVersion2) { if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Official || endpoint.Version == registry.APIVersion2) {
j := job.Eng.Job("trust_update_base") j := job.Eng.Job("trust_update_base")
if err = j.Run(); err != nil { if err = j.Run(); err != nil {
return job.Errorf("error updating trust base graph: %s", err) return job.Errorf("error updating trust base graph: %s", err)
} }
if err := s.pullV2Repository(job.Eng, r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err == nil { if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil {
if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil {
log.Errorf("Error logging event 'pull' for %s: %s", logName, err) log.Errorf("Error logging event 'pull' for %s: %s", logName, err)
} }
@ -158,7 +143,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
} }
} }
if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel"), mirrors); err != nil { if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil {
return job.Error(err) return job.Error(err)
} }
@ -169,20 +154,20 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
return engine.StatusOK return engine.StatusOK
} }
func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool, mirrors []string) error { func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
out.Write(sf.FormatStatus("", "Pulling repository %s", localName)) out.Write(sf.FormatStatus("", "Pulling repository %s", repoInfo.CanonicalName))
repoData, err := r.GetRepositoryData(remoteName) repoData, err := r.GetRepositoryData(repoInfo.RemoteName)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "HTTP code: 404") { if strings.Contains(err.Error(), "HTTP code: 404") {
return fmt.Errorf("Error: image %s:%s not found", remoteName, askedTag) return fmt.Errorf("Error: image %s:%s not found", repoInfo.RemoteName, askedTag)
} }
// Unexpected HTTP error // Unexpected HTTP error
return err return err
} }
log.Debugf("Retrieving the tag list") log.Debugf("Retrieving the tag list")
tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens)
if err != nil { if err != nil {
log.Errorf("%v", err) log.Errorf("%v", err)
return err return err
@ -207,7 +192,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
// Otherwise, check that the tag exists and use only that one // Otherwise, check that the tag exists and use only that one
id, exists := tagsList[askedTag] id, exists := tagsList[askedTag]
if !exists { if !exists {
return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName) return fmt.Errorf("Tag %s not found in repository %s", askedTag, repoInfo.CanonicalName)
} }
imageId = id imageId = id
repoData.ImgList[id].Tag = askedTag repoData.ImgList[id].Tag = askedTag
@ -250,31 +235,29 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
} }
defer s.poolRemove("pull", "img:"+img.ID) defer s.poolRemove("pull", "img:"+img.ID)
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil)) out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil))
success := false success := false
var lastErr, err error var lastErr, err error
var is_downloaded bool var is_downloaded bool
if mirrors != nil { for _, ep := range repoInfo.Index.Mirrors {
for _, ep := range mirrors { out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, localName, ep), nil))
if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
// Don't report errors when pulling from mirrors. // Don't report errors when pulling from mirrors.
log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, localName, ep, err) log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err)
continue continue
} }
layers_downloaded = layers_downloaded || is_downloaded layers_downloaded = layers_downloaded || is_downloaded
success = true success = true
break break
} }
}
if !success { if !success {
for _, ep := range repoData.Endpoints { for _, ep := range repoData.Endpoints {
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil)) out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
// It's not ideal that only the last error is returned, it would be better to concatenate the errors. // It's not ideal that only the last error is returned, it would be better to concatenate the errors.
// As the error is also given to the output stream the user will see the error. // As the error is also given to the output stream the user will see the error.
lastErr = err lastErr = err
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil)) out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil))
continue continue
} }
layers_downloaded = layers_downloaded || is_downloaded layers_downloaded = layers_downloaded || is_downloaded
@ -283,7 +266,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
} }
} }
if !success { if !success {
err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr) err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, repoInfo.CanonicalName, lastErr)
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil)) out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil))
if parallel { if parallel {
errors <- err errors <- err
@ -319,14 +302,14 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
if askedTag != "" && id != imageId { if askedTag != "" && id != imageId {
continue continue
} }
if err := s.Set(localName, tag, id, true); err != nil { if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil {
return err return err
} }
} }
requestedTag := localName requestedTag := repoInfo.CanonicalName
if len(askedTag) > 0 { if len(askedTag) > 0 {
requestedTag = localName + ":" + askedTag requestedTag = repoInfo.CanonicalName + ":" + askedTag
} }
WriteStatus(requestedTag, out, sf, layers_downloaded) WriteStatus(requestedTag, out, sf, layers_downloaded)
return nil return nil
@ -440,40 +423,40 @@ type downloadInfo struct {
err chan error err chan error
} }
func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, localName, remoteName, tag string, sf *utils.StreamFormatter, parallel bool) error { func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool) error {
var layersDownloaded bool var layersDownloaded bool
if tag == "" { if tag == "" {
log.Debugf("Pulling tag list from V2 registry for %s", remoteName) log.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName)
tags, err := r.GetV2RemoteTags(remoteName, nil) tags, err := r.GetV2RemoteTags(repoInfo.RemoteName, nil)
if err != nil { if err != nil {
return err return err
} }
for _, t := range tags { for _, t := range tags {
if downloaded, err := s.pullV2Tag(eng, r, out, localName, remoteName, t, sf, parallel); err != nil { if downloaded, err := s.pullV2Tag(eng, r, out, repoInfo, t, sf, parallel); err != nil {
return err return err
} else if downloaded { } else if downloaded {
layersDownloaded = true layersDownloaded = true
} }
} }
} else { } else {
if downloaded, err := s.pullV2Tag(eng, r, out, localName, remoteName, tag, sf, parallel); err != nil { if downloaded, err := s.pullV2Tag(eng, r, out, repoInfo, tag, sf, parallel); err != nil {
return err return err
} else if downloaded { } else if downloaded {
layersDownloaded = true layersDownloaded = true
} }
} }
requestedTag := localName requestedTag := repoInfo.CanonicalName
if len(tag) > 0 { if len(tag) > 0 {
requestedTag = localName + ":" + tag requestedTag = repoInfo.CanonicalName + ":" + tag
} }
WriteStatus(requestedTag, out, sf, layersDownloaded) WriteStatus(requestedTag, out, sf, layersDownloaded)
return nil return nil
} }
func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, localName, remoteName, tag string, sf *utils.StreamFormatter, parallel bool) (bool, error) { func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool) (bool, error) {
log.Debugf("Pulling tag from V2 registry: %q", tag) log.Debugf("Pulling tag from V2 registry: %q", tag)
manifestBytes, err := r.GetV2ImageManifest(remoteName, tag, nil) manifestBytes, err := r.GetV2ImageManifest(repoInfo.RemoteName, tag, nil)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -488,9 +471,9 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
} }
if verified { if verified {
out.Write(sf.FormatStatus(localName+":"+tag, "The image you are pulling has been verified")) out.Write(sf.FormatStatus(repoInfo.CanonicalName+":"+tag, "The image you are pulling has been verified"))
} else { } else {
out.Write(sf.FormatStatus(tag, "Pulling from %s", localName)) out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
} }
if len(manifest.FSLayers) == 0 { if len(manifest.FSLayers) == 0 {
@ -542,7 +525,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
return err return err
} }
r, l, err := r.GetV2ImageBlobReader(remoteName, sumType, checksum, nil) r, l, err := r.GetV2ImageBlobReader(repoInfo.RemoteName, sumType, checksum, nil)
if err != nil { if err != nil {
return err return err
} }
@ -605,7 +588,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
} }
if err = s.Set(localName, tag, downloads[0].img.ID, true); err != nil { if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
return false, err return false, err
} }

View File

@ -61,7 +61,7 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
return imageList, tagsByImage, nil return imageList, tagsByImage, nil
} }
func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error { func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
log.Debugf("Local repo: %s", localRepo) log.Debugf("Local repo: %s", localRepo)
imgList, tagsByImage, err := s.getImageList(localRepo, tag) imgList, tagsByImage, err := s.getImageList(localRepo, tag)
@ -104,7 +104,7 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName,
// Register all the images in a repository with the registry // Register all the images in a repository with the registry
// If an image is not in this list it will not be associated with the repository // If an image is not in this list it will not be associated with the repository
repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) repoData, err = r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, false, nil)
if err != nil { if err != nil {
return err return err
} }
@ -114,11 +114,11 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName,
nTag = len(localRepo) nTag = len(localRepo)
} }
for _, ep := range repoData.Endpoints { for _, ep := range repoData.Endpoints {
out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag)) out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", repoInfo.CanonicalName, nTag))
for _, imgId := range imgList { for _, imgId := range imgList {
if err := r.LookupRemoteImage(imgId, ep, repoData.Tokens); err != nil { if err := r.LookupRemoteImage(imgId, ep, repoData.Tokens); err != nil {
log.Errorf("Error in LookupRemoteImage: %s", err) log.Errorf("Error in LookupRemoteImage: %s", err)
if _, err := s.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil { if _, err := s.pushImage(r, out, imgId, ep, repoData.Tokens, sf); err != nil {
// FIXME: Continue on error? // FIXME: Continue on error?
return err return err
} }
@ -126,23 +126,23 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName,
out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId))) out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
} }
for _, tag := range tagsByImage[imgId] { for _, tag := range tagsByImage[imgId] {
out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+repoInfo.RemoteName+"/tags/"+tag))
if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { if err := r.PushRegistryTag(repoInfo.RemoteName, imgId, tag, ep, repoData.Tokens); err != nil {
return err return err
} }
} }
} }
} }
if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil { if _, err := r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, true, repoData.Endpoints); err != nil {
return err return err
} }
return nil return nil
} }
func (s *TagStore) pushImage(r *registry.Session, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
if err != nil { if err != nil {
@ -199,26 +199,27 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
metaHeaders map[string][]string metaHeaders map[string][]string
) )
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
if err != nil {
return job.Error(err)
}
tag := job.Getenv("tag") tag := job.Getenv("tag")
job.GetenvJson("authConfig", authConfig) job.GetenvJson("authConfig", authConfig)
job.GetenvJson("metaHeaders", &metaHeaders) job.GetenvJson("metaHeaders", &metaHeaders)
if _, err := s.poolAdd("push", localName); err != nil {
if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil {
return job.Error(err) return job.Error(err)
} }
defer s.poolRemove("push", localName) defer s.poolRemove("push", repoInfo.LocalName)
// Resolve the Repository name from fqn to endpoint + name endpoint, err := repoInfo.GetEndpoint()
hostname, remoteName, err := registry.ResolveRepositoryName(localName)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries) img, err := s.graph.Get(repoInfo.LocalName)
if err != nil {
return job.Error(err)
}
img, err := s.graph.Get(localName)
r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
if err2 != nil { if err2 != nil {
return job.Error(err2) return job.Error(err2)
@ -227,12 +228,12 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
if err != nil { if err != nil {
reposLen := 1 reposLen := 1
if tag == "" { if tag == "" {
reposLen = len(s.Repositories[localName]) reposLen = len(s.Repositories[repoInfo.LocalName])
} }
job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
// If it fails, try to get the repository // If it fails, try to get the repository
if localRepo, exists := s.Repositories[localName]; exists { if localRepo, exists := s.Repositories[repoInfo.LocalName]; exists {
if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil { if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil {
return job.Error(err) return job.Error(err)
} }
return engine.StatusOK return engine.StatusOK
@ -241,8 +242,8 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
} }
var token []string var token []string
job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", repoInfo.CanonicalName))
if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint.String(), token, sf); err != nil { if _, err := s.pushImage(r, job.Stdout, img.ID, endpoint.String(), token, sf); err != nil {
return job.Error(err) return job.Error(err)
} }
return engine.StatusOK return engine.StatusOK

View File

@ -13,6 +13,7 @@ import (
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -25,8 +26,6 @@ var (
type TagStore struct { type TagStore struct {
path string path string
graph *Graph graph *Graph
mirrors []string
insecureRegistries []string
Repositories map[string]Repository Repositories map[string]Repository
sync.Mutex sync.Mutex
// FIXME: move push/pull-related fields // FIXME: move push/pull-related fields
@ -55,7 +54,7 @@ func (r Repository) Contains(u Repository) bool {
return true return true
} }
func NewTagStore(path string, graph *Graph, mirrors []string, insecureRegistries []string) (*TagStore, error) { func NewTagStore(path string, graph *Graph) (*TagStore, error) {
abspath, err := filepath.Abs(path) abspath, err := filepath.Abs(path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,8 +63,6 @@ func NewTagStore(path string, graph *Graph, mirrors []string, insecureRegistries
store := &TagStore{ store := &TagStore{
path: abspath, path: abspath,
graph: graph, graph: graph,
mirrors: mirrors,
insecureRegistries: insecureRegistries,
Repositories: make(map[string]Repository), Repositories: make(map[string]Repository),
pullingPool: make(map[string]chan struct{}), pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}), pushingPool: make(map[string]chan struct{}),
@ -178,6 +175,7 @@ func (store *TagStore) Delete(repoName, tag string) (bool, error) {
if err := store.reload(); err != nil { if err := store.reload(); err != nil {
return false, err return false, err
} }
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists { if r, exists := store.Repositories[repoName]; exists {
if tag != "" { if tag != "" {
if _, exists2 := r[tag]; exists2 { if _, exists2 := r[tag]; exists2 {
@ -219,6 +217,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err return err
} }
var repo Repository var repo Repository
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists { if r, exists := store.Repositories[repoName]; exists {
repo = r repo = r
if old, exists := store.Repositories[repoName][tag]; exists && !force { if old, exists := store.Repositories[repoName][tag]; exists && !force {
@ -238,6 +237,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
if err := store.reload(); err != nil { if err := store.reload(); err != nil {
return nil, err return nil, err
} }
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists { if r, exists := store.Repositories[repoName]; exists {
return r, nil return r, nil
} }
@ -279,20 +279,6 @@ func (store *TagStore) GetRepoRefs() map[string][]string {
return reporefs return reporefs
} }
// isOfficialName returns whether a repo name is considered an official
// repository. Official repositories are repos with names within
// the library namespace or which default to the library namespace
// by not providing one.
func isOfficialName(name string) bool {
if strings.HasPrefix(name, "library/") {
return true
}
if strings.IndexRune(name, '/') == -1 {
return true
}
return false
}
// Validate the name of a repository // Validate the name of a repository
func validateRepoName(name string) error { func validateRepoName(name string) error {
if name == "" { if name == "" {

View File

@ -15,8 +15,12 @@ import (
) )
const ( const (
testImageName = "myapp" testOfficialImageName = "myapp"
testImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234" testOfficialImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234"
testOfficialImageIDShort = "1a2d3c4d4e5f"
testPrivateImageName = "127.0.0.1:8000/privateapp"
testPrivateImageID = "5bc255f8699e4ee89ac4469266c3d11515da88fdcbde45d7b069b636ff4efd81"
testPrivateImageIDShort = "5bc255f8699e"
) )
func fakeTar() (io.Reader, error) { func fakeTar() (io.Reader, error) {
@ -53,19 +57,30 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil) store, err := NewTagStore(path.Join(root, "tags"), graph)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
archive, err := fakeTar() officialArchive, err := fakeTar()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
img := &image.Image{ID: testImageID} img := &image.Image{ID: testOfficialImageID}
if err := graph.Register(img, archive); err != nil { if err := graph.Register(img, officialArchive); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := store.Set(testImageName, "", testImageID, false); err != nil { if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil {
t.Fatal(err)
}
privateArchive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
img = &image.Image{ID: testPrivateImageID}
if err := graph.Register(img, privateArchive); err != nil {
t.Fatal(err)
}
if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil {
t.Fatal(err) t.Fatal(err)
} }
return store return store
@ -80,39 +95,65 @@ func TestLookupImage(t *testing.T) {
store := mkTestTagStore(tmp, t) store := mkTestTagStore(tmp, t)
defer store.graph.driver.Cleanup() defer store.graph.driver.Cleanup()
if img, err := store.LookupImage(testImageName); err != nil { officialLookups := []string{
t.Fatal(err) testOfficialImageID,
} else if img == nil { testOfficialImageIDShort,
t.Errorf("Expected 1 image, none found") testOfficialImageName + ":" + testOfficialImageID,
} testOfficialImageName + ":" + testOfficialImageIDShort,
if img, err := store.LookupImage(testImageName + ":" + DEFAULTTAG); err != nil { testOfficialImageName,
t.Fatal(err) testOfficialImageName + ":" + DEFAULTTAG,
} else if img == nil { "docker.io/" + testOfficialImageName,
t.Errorf("Expected 1 image, none found") "docker.io/" + testOfficialImageName + ":" + DEFAULTTAG,
"index.docker.io/" + testOfficialImageName,
"index.docker.io/" + testOfficialImageName + ":" + DEFAULTTAG,
"library/" + testOfficialImageName,
"library/" + testOfficialImageName + ":" + DEFAULTTAG,
"docker.io/library/" + testOfficialImageName,
"docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG,
"index.docker.io/library/" + testOfficialImageName,
"index.docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG,
} }
if img, err := store.LookupImage(testImageName + ":" + "fail"); err == nil { privateLookups := []string{
t.Errorf("Expected error, none found") testPrivateImageID,
testPrivateImageIDShort,
testPrivateImageName + ":" + testPrivateImageID,
testPrivateImageName + ":" + testPrivateImageIDShort,
testPrivateImageName,
testPrivateImageName + ":" + DEFAULTTAG,
}
invalidLookups := []string{
testOfficialImageName + ":" + "fail",
"fail:fail",
}
for _, name := range officialLookups {
if img, err := store.LookupImage(name); err != nil {
t.Errorf("Error looking up %s: %s", name, err)
} else if img == nil {
t.Errorf("Expected 1 image, none found: %s", name)
} else if img.ID != testOfficialImageID {
t.Errorf("Expected ID '%s' found '%s'", testOfficialImageID, img.ID)
}
}
for _, name := range privateLookups {
if img, err := store.LookupImage(name); err != nil {
t.Errorf("Error looking up %s: %s", name, err)
} else if img == nil {
t.Errorf("Expected 1 image, none found: %s", name)
} else if img.ID != testPrivateImageID {
t.Errorf("Expected ID '%s' found '%s'", testPrivateImageID, img.ID)
}
}
for _, name := range invalidLookups {
if img, err := store.LookupImage(name); err == nil {
t.Errorf("Expected error, none found: %s", name)
} else if img != nil { } else if img != nil {
t.Errorf("Expected 0 image, 1 found") t.Errorf("Expected 0 image, 1 found: %s", name)
} }
if img, err := store.LookupImage("fail:fail"); err == nil {
t.Errorf("Expected error, none found")
} else if img != nil {
t.Errorf("Expected 0 image, 1 found")
}
if img, err := store.LookupImage(testImageID); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
if img, err := store.LookupImage(testImageName + ":" + testImageID); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
} }
} }
@ -133,18 +174,3 @@ func TestInvalidTagName(t *testing.T) {
} }
} }
} }
func TestOfficialName(t *testing.T) {
names := map[string]bool{
"library/ubuntu": true,
"nonlibrary/ubuntu": false,
"ubuntu": true,
"other/library": false,
}
for name, isOfficial := range names {
result := isOfficialName(name)
if result != isOfficial {
t.Errorf("Unexpected result for %s\n\tExpecting: %v\n\tActual: %v", name, isOfficial, result)
}
}
}

View File

@ -4301,3 +4301,24 @@ func TestBuildRenamedDockerfile(t *testing.T) {
logDone("build - rename dockerfile") logDone("build - rename dockerfile")
} }
func TestBuildFromOfficialNames(t *testing.T) {
name := "testbuildfromofficial"
fromNames := []string{
"busybox",
"docker.io/busybox",
"index.docker.io/busybox",
"library/busybox",
"docker.io/library/busybox",
"index.docker.io/library/busybox",
}
for idx, fromName := range fromNames {
imgName := fmt.Sprintf("%s%d", name, idx)
_, err := buildImage(imgName, "FROM "+fromName, true)
if err != nil {
t.Errorf("Build failed using FROM %s: %s", fromName, err)
}
deleteImages(imgName)
}
logDone("build - from official names")
}

View File

@ -2,6 +2,7 @@ package main
import ( import (
"os/exec" "os/exec"
"strings"
"testing" "testing"
) )
@ -24,3 +25,33 @@ func TestPullNonExistingImage(t *testing.T) {
} }
logDone("pull - pull fooblahblah1234 (non-existing image)") logDone("pull - pull fooblahblah1234 (non-existing image)")
} }
// pulling an image from the central registry using official names should work
// ensure all pulls result in the same image
func TestPullImageOfficialNames(t *testing.T) {
names := []string{
"docker.io/hello-world",
"index.docker.io/hello-world",
"library/hello-world",
"docker.io/library/hello-world",
"index.docker.io/library/hello-world",
}
for _, name := range names {
pullCmd := exec.Command(dockerBinary, "pull", name)
out, exitCode, err := runCommandWithOutput(pullCmd)
if err != nil || exitCode != 0 {
t.Errorf("pulling the '%s' image from the registry has failed: %s", name, err)
continue
}
// ensure we don't have multiple image names.
imagesCmd := exec.Command(dockerBinary, "images")
out, _, err = runCommandWithOutput(imagesCmd)
if err != nil {
t.Errorf("listing images failed with errors: %v", err)
} else if strings.Contains(out, name) {
t.Errorf("images should not have listed '%s'", name)
}
}
logDone("pull - pull official names")
}

View File

@ -132,3 +132,49 @@ func TestTagExistedNameWithForce(t *testing.T) {
logDone("tag - busybox with an existed tag name with -f option work") logDone("tag - busybox with an existed tag name with -f option work")
} }
// ensure tagging using official names works
// ensure all tags result in the same name
func TestTagOfficialNames(t *testing.T) {
names := []string{
"docker.io/busybox",
"index.docker.io/busybox",
"library/busybox",
"docker.io/library/busybox",
"index.docker.io/library/busybox",
}
for _, name := range names {
tagCmd := exec.Command(dockerBinary, "tag", "-f", "busybox:latest", name+":latest")
out, exitCode, err := runCommandWithOutput(tagCmd)
if err != nil || exitCode != 0 {
t.Errorf("tag busybox %v should have worked: %s, %s", name, err, out)
continue
}
// ensure we don't have multiple tag names.
imagesCmd := exec.Command(dockerBinary, "images")
out, _, err = runCommandWithOutput(imagesCmd)
if err != nil {
t.Errorf("listing images failed with errors: %v, %s", err, out)
} else if strings.Contains(out, name) {
t.Errorf("images should not have listed '%s'", name)
deleteImages(name + ":latest")
} else {
logMessage := fmt.Sprintf("tag official name - busybox %v", name)
logDone(logMessage)
}
}
for _, name := range names {
tagCmd := exec.Command(dockerBinary, "tag", "-f", name+":latest", "fooo/bar:latest")
_, exitCode, err := runCommandWithOutput(tagCmd)
if err != nil || exitCode != 0 {
t.Errorf("tag %v fooo/bar should have worked: %s", name, err)
continue
}
deleteImages("fooo/bar:latest")
logMessage := fmt.Sprintf("tag official name - %v fooo/bar", name)
logDone(logMessage)
}
}

View File

@ -20,6 +20,7 @@ import (
"github.com/docker/docker/daemon" "github.com/docker/docker/daemon"
"github.com/docker/docker/engine" "github.com/docker/docker/engine"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -173,7 +174,14 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
eng := engine.New() eng := engine.New()
eng.Logging = false eng.Logging = false
// Load default plugins // Load default plugins
builtins.Register(eng) if err := builtins.Register(eng); err != nil {
t.Fatal(err)
}
// load registry service
if err := registry.NewService(nil).Install(eng); err != nil {
t.Fatal(err)
}
// (This is manually copied and modified from main() until we have a more generic plugin system) // (This is manually copied and modified from main() until we have a more generic plugin system)
cfg := &daemon.Config{ cfg := &daemon.Config{
Root: root, Root: root,

View File

@ -3,7 +3,6 @@ package opts
import ( import (
"fmt" "fmt"
"net" "net"
"net/url"
"os" "os"
"path" "path"
"regexp" "regexp"
@ -39,10 +38,6 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) {
flag.Var(NewIpOpt(value, defaultValue), names, usage) flag.Var(NewIpOpt(value, defaultValue), names, usage)
} }
func MirrorListVar(values *[]string, names []string, usage string) {
flag.Var(newListOptsRef(values, ValidateMirror), names, usage)
}
func LabelListVar(values *[]string, names []string, usage string) { func LabelListVar(values *[]string, names []string, usage string) {
flag.Var(newListOptsRef(values, ValidateLabel), names, usage) flag.Var(newListOptsRef(values, ValidateLabel), names, usage)
} }
@ -127,6 +122,7 @@ func (opts *ListOpts) Len() int {
// Validators // Validators
type ValidatorFctType func(val string) (string, error) type ValidatorFctType func(val string) (string, error)
type ValidatorFctListType func(val string) ([]string, error)
func ValidateAttach(val string) (string, error) { func ValidateAttach(val string) (string, error) {
s := strings.ToLower(val) s := strings.ToLower(val)
@ -214,24 +210,6 @@ func ValidateExtraHost(val string) (string, error) {
return val, nil return val, nil
} }
// 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
}
func ValidateLabel(val string) (string, error) { func ValidateLabel(val string) (string, error) {
if strings.Count(val, "=") != 1 { if strings.Count(val, "=") != 1 {
return "", fmt.Errorf("bad attribute format: %s", val) return "", fmt.Errorf("bad attribute format: %s", val)

View File

@ -30,7 +30,23 @@ func TestValidateIPAddress(t *testing.T) {
func TestListOpts(t *testing.T) { func TestListOpts(t *testing.T) {
o := NewListOpts(nil) o := NewListOpts(nil)
o.Set("foo") o.Set("foo")
o.String() if o.String() != "[foo]" {
t.Errorf("%s != [foo]", o.String())
}
o.Set("bar")
if o.Len() != 2 {
t.Errorf("%d != 2", o.Len())
}
if !o.Get("bar") {
t.Error("o.Get(\"bar\") == false")
}
if o.Get("baz") {
t.Error("o.Get(\"baz\") == true")
}
o.Delete("foo")
if o.String() != "[bar]" {
t.Errorf("%s != [bar]", o.String())
}
} }
func TestValidateDnsSearch(t *testing.T) { func TestValidateDnsSearch(t *testing.T) {

View File

@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"os" "os"
"path" "path"
"strings" "strings"
@ -18,27 +17,12 @@ 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/"
// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
) )
var ( var (
ErrConfigFileMissing = errors.New("The Auth config file is missing") ErrConfigFileMissing = errors.New("The Auth config file is missing")
IndexServerURL *url.URL
) )
func init() {
url, err := url.Parse(INDEXSERVER)
if err != nil {
panic(err)
}
IndexServerURL = url
}
type AuthConfig struct { type AuthConfig struct {
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"` Password string `json:"password,omitempty"`
@ -52,10 +36,6 @@ type ConfigFile struct {
rootPath string rootPath string
} }
func IndexServerAddress() string {
return INDEXSERVER
}
// 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
@ -118,6 +98,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
} }
authConfig.Email = origEmail[1] authConfig.Email = origEmail[1]
authConfig.ServerAddress = IndexServerAddress() authConfig.ServerAddress = IndexServerAddress()
// *TODO: Switch to using IndexServerName() instead?
configFile.Configs[IndexServerAddress()] = authConfig configFile.Configs[IndexServerAddress()] = authConfig
} else { } else {
for k, authConfig := range configFile.Configs { for k, authConfig := range configFile.Configs {
@ -181,7 +162,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
) )
if serverAddress == "" { if serverAddress == "" {
serverAddress = IndexServerAddress() return "", fmt.Errorf("Server Error: Server Address not set.")
} }
loginAgainstOfficialIndex := serverAddress == IndexServerAddress() loginAgainstOfficialIndex := serverAddress == IndexServerAddress()
@ -213,6 +194,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
status = "Account created. Please use the confirmation link we sent" + status = "Account created. Please use the confirmation link we sent" +
" to your e-mail to activate it." " to your e-mail to activate it."
} else { } else {
// *TODO: Use registry configuration to determine what this says, if anything?
status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
} }
} else if reqStatusCode == 400 { } else if reqStatusCode == 400 {
@ -236,6 +218,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
if loginAgainstOfficialIndex { if loginAgainstOfficialIndex {
return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.") return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
} }
// *TODO: Use registry configuration to determine what this says, if anything?
return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
} }
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header) return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
@ -271,14 +254,10 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
} }
// this method matches a auth configuration to a server address or a url // this method matches a auth configuration to a server address or a url
func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig { func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
if hostname == IndexServerAddress() || len(hostname) == 0 { configKey := index.GetAuthConfigKey()
// default to the index server
return config.Configs[IndexServerAddress()]
}
// First try the happy case // First try the happy case
if c, found := config.Configs[hostname]; found { if c, found := config.Configs[configKey]; found || index.Official {
return c return c
} }
@ -297,9 +276,8 @@ func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
// Maybe they have a legacy config file, we will iterate the keys converting // Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing // them to the new format and testing
normalizedHostename := convertToHostname(hostname)
for registry, config := range config.Configs { for registry, config := range config.Configs {
if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename { if configKey == convertToHostname(registry) {
return config return config
} }
} }

View File

@ -81,12 +81,20 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
} }
defer os.RemoveAll(configFile.rootPath) defer os.RemoveAll(configFile.rootPath)
for _, registry := range []string{"", IndexServerAddress()} { indexConfig := configFile.Configs[IndexServerAddress()]
resolved := configFile.ResolveAuthConfig(registry)
if resolved != configFile.Configs[IndexServerAddress()] { officialIndex := &IndexInfo{
t.Fail() Official: true,
} }
privateIndex := &IndexInfo{
Official: false,
} }
resolved := configFile.ResolveAuthConfig(officialIndex)
assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
resolved = configFile.ResolveAuthConfig(privateIndex)
assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
} }
func TestResolveAuthConfigFullURL(t *testing.T) { func TestResolveAuthConfigFullURL(t *testing.T) {
@ -106,18 +114,27 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
Password: "bar-pass", Password: "bar-pass",
Email: "bar@example.com", Email: "bar@example.com",
} }
configFile.Configs["https://registry.example.com/v1/"] = registryAuth officialAuth := AuthConfig{
configFile.Configs["http://localhost:8000/v1/"] = localAuth Username: "baz-user",
configFile.Configs["registry.com"] = registryAuth Password: "baz-pass",
Email: "baz@example.com",
}
configFile.Configs[IndexServerAddress()] = officialAuth
expectedAuths := map[string]AuthConfig{
"registry.example.com": registryAuth,
"localhost:8000": localAuth,
"registry.com": localAuth,
}
validRegistries := map[string][]string{ validRegistries := map[string][]string{
"https://registry.example.com/v1/": { "registry.example.com": {
"https://registry.example.com/v1/", "https://registry.example.com/v1/",
"http://registry.example.com/v1/", "http://registry.example.com/v1/",
"registry.example.com", "registry.example.com",
"registry.example.com/v1/", "registry.example.com/v1/",
}, },
"http://localhost:8000/v1/": { "localhost:8000": {
"https://localhost:8000/v1/", "https://localhost:8000/v1/",
"http://localhost:8000/v1/", "http://localhost:8000/v1/",
"localhost:8000", "localhost:8000",
@ -132,18 +149,24 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
} }
for configKey, registries := range validRegistries { for configKey, registries := range validRegistries {
for _, registry := range registries { configured, ok := expectedAuths[configKey]
var ( if !ok || configured.Email == "" {
configured AuthConfig t.Fatal()
ok bool
)
resolved := configFile.ResolveAuthConfig(registry)
if configured, ok = configFile.Configs[configKey]; !ok {
t.Fail()
} }
index := &IndexInfo{
Name: configKey,
}
for _, registry := range registries {
configFile.Configs[registry] = configured
resolved := configFile.ResolveAuthConfig(index)
if resolved.Email != configured.Email { if resolved.Email != configured.Email {
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
} }
delete(configFile.Configs, registry)
resolved = configFile.ResolveAuthConfig(index)
if resolved.Email == configured.Email {
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
}
} }
} }
} }

382
registry/config.go Normal file
View File

@ -0,0 +1,382 @@
package registry
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"regexp"
"strings"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/utils"
)
// Options holds command line options.
type Options struct {
Mirrors 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
// the current process.
func (options *Options) InstallFlags() {
options.Mirrors = opts.NewListOpts(ValidateMirror)
flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
options.InsecureRegistries = opts.NewListOpts(ValidateIndexName)
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)")
}
type netIPNet net.IPNet
func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(ipnet).String())
}
func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
var ipnet_str string
if err = json.Unmarshal(b, &ipnet_str); err == nil {
var cidr *net.IPNet
if _, cidr, err = net.ParseCIDR(ipnet_str); err == nil {
*ipnet = netIPNet(*cidr)
}
}
return
}
// ServiceConfig stores daemon registry services configuration.
type ServiceConfig struct {
InsecureRegistryCIDRs []*netIPNet `json:"InsecureRegistryCIDRs"`
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
}
// NewServiceConfig returns a new instance of ServiceConfig
func NewServiceConfig(options *Options) *ServiceConfig {
if options == nil {
options = &Options{
Mirrors: opts.NewListOpts(nil),
InsecureRegistries: opts.NewListOpts(nil),
}
}
// Localhost is by default considered as an insecure registry
// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
//
// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
// daemon flags on boot2docker?
options.InsecureRegistries.Set("127.0.0.0/8")
config := &ServiceConfig{
InsecureRegistryCIDRs: make([]*netIPNet, 0),
IndexConfigs: make(map[string]*IndexInfo, 0),
}
// Split --insecure-registry into CIDR and registry-specific settings.
for _, r := range options.InsecureRegistries.GetAll() {
// Check if CIDR was passed to --insecure-registry
_, ipnet, err := net.ParseCIDR(r)
if err == nil {
// Valid CIDR.
config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*netIPNet)(ipnet))
} else {
// Assume `host:port` if not CIDR.
config.IndexConfigs[r] = &IndexInfo{
Name: r,
Mirrors: make([]string, 0),
Secure: false,
Official: false,
}
}
}
// Configure public registry.
config.IndexConfigs[IndexServerName()] = &IndexInfo{
Name: IndexServerName(),
Mirrors: options.Mirrors.GetAll(),
Secure: true,
Official: true,
}
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
}

49
registry/config_test.go Normal file
View File

@ -0,0 +1,49 @@
package registry
import (
"testing"
)
func TestValidateMirror(t *testing.T) {
valid := []string{
"http://mirror-1.com",
"https://mirror-1.com",
"http://localhost",
"https://localhost",
"http://localhost:5000",
"https://localhost:5000",
"http://127.0.0.1",
"https://127.0.0.1",
"http://127.0.0.1:5000",
"https://127.0.0.1:5000",
}
invalid := []string{
"!invalid!://%as%",
"ftp://mirror-1.com",
"http://mirror-1.com/",
"http://mirror-1.com/?q=foo",
"http://mirror-1.com/v1/",
"http://mirror-1.com/v1/?q=foo",
"http://mirror-1.com/v1/?q=foo#frag",
"http://mirror-1.com?q=foo",
"https://mirror-1.com#frag",
"https://mirror-1.com/",
"https://mirror-1.com/#frag",
"https://mirror-1.com/v1/",
"https://mirror-1.com/v1/#",
"https://mirror-1.com?q",
}
for _, address := range valid {
if ret, err := ValidateMirror(address); err != nil || ret == "" {
t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
}
}
for _, address := range invalid {
if ret, err := ValidateMirror(address); err == nil || ret != "" {
t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
}
}
}

View File

@ -37,8 +37,9 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
return hostname, DefaultAPIVersion return hostname, DefaultAPIVersion
} }
func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) { func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname, insecureRegistries) // *TODO: Allow per-registry configuration of endpoints.
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,7 +50,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
//TODO: triggering highland build can be done there without "failing" //TODO: triggering highland build can be done there without "failing"
if endpoint.secure { if index.Secure {
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` // 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. // 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) 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)
@ -68,7 +69,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
return endpoint, nil return endpoint, nil
} }
func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) { func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var ( var (
endpoint = Endpoint{} endpoint = Endpoint{}
trimmedHostname string trimmedHostname string
@ -82,13 +83,14 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries) endpoint.secure = secure
if err != nil {
return nil, err
}
return &endpoint, nil return &endpoint, nil
} }
func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
return NewEndpoint(repoInfo.Index)
}
type Endpoint struct { type Endpoint struct {
URL *url.URL URL *url.URL
Version APIVersion Version APIVersion
@ -155,63 +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
} }
// isSecure returns false if the provided hostname 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 hostname, the latter is considered
// insecure.
//
// hostname 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, isSecure will only try to match hostname to any element
// of insecureRegistries.
func isSecure(hostname string, insecureRegistries []string) (bool, error) {
if hostname == IndexServerURL.Host {
return true, nil
}
host, _, err := net.SplitHostPort(hostname)
if err != nil {
// assume hostname is of the form `host` without the port and go on.
host = hostname
}
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.
}
for _, r := range insecureRegistries {
if hostname == r {
// hostname matches insecure registry
return false, nil
}
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
for _, addr := range addrs {
// now assume a CIDR was passed to --insecure-registry
_, ipnet, err := net.ParseCIDR(r)
if err != nil {
// if we could not parse it as a CIDR, even after removing
// assume it's not a CIDR and go on with the next candidate
break
}
// check if the addr falls in the subnet
if ipnet.Contains(addr) {
return false, nil
}
}
}
return true, nil
}

View File

@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"}, {"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
} }
for _, td := range testData { for _, td := range testData {
e, err := newEndpoint(td.str, insecureRegistries) e, err := newEndpoint(td.str, false)
if err != nil { if err != nil {
t.Errorf("%q: %s", td.str, err) t.Errorf("%q: %s", td.str, err)
} }

View File

@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"regexp"
"strings" "strings"
"time" "time"
@ -20,11 +19,8 @@ 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-_.]+)$`)
) )
type TimeoutType uint32 type TimeoutType uint32
@ -160,67 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
return res, client, err return res, client, err
} }
func validateRepositoryName(repositoryName string) error {
var (
namespace string
name string
)
nameParts := strings.SplitN(repositoryName, "/", 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
}
// Resolves a repository name to a hostname + name
func ResolveRepositoryName(reposName string) (string, string, error) {
if strings.Contains(reposName, "://") {
// It cannot contain a scheme!
return "", "", ErrInvalidRepositoryName
}
nameParts := strings.SplitN(reposName, "/", 2)
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)
err := validateRepositoryName(reposName)
return IndexServerAddress(), reposName, err
}
hostname := nameParts[0]
reposName = nameParts[1]
if strings.Contains(hostname, "index.docker.io") {
return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
}
if err := validateRepositoryName(reposName); err != nil {
return "", "", err
}
return hostname, reposName, nil
}
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"}

View File

@ -15,6 +15,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/docker/docker/opts"
"github.com/gorilla/mux" "github.com/gorilla/mux"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
@ -22,7 +23,7 @@ import (
var ( var (
testHTTPServer *httptest.Server testHTTPServer *httptest.Server
insecureRegistries []string testHTTPSServer *httptest.Server
testLayers = map[string]map[string]string{ testLayers = map[string]map[string]string{
"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": { "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
@ -86,6 +87,7 @@ var (
"": {net.ParseIP("0.0.0.0")}, "": {net.ParseIP("0.0.0.0")},
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
"example.com": {net.ParseIP("42.42.42.42")}, "example.com": {net.ParseIP("42.42.42.42")},
"other.com": {net.ParseIP("43.43.43.43")},
} }
) )
@ -108,11 +110,7 @@ func init() {
r.HandleFunc("/v2/version", handlerGetPing).Methods("GET") r.HandleFunc("/v2/version", handlerGetPing).Methods("GET")
testHTTPServer = httptest.NewServer(handlerAccessLog(r)) testHTTPServer = httptest.NewServer(handlerAccessLog(r))
URL, err := url.Parse(testHTTPServer.URL) testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r))
if err != nil {
panic(err)
}
insecureRegistries = []string{URL.Host}
// override net.LookupIP // override net.LookupIP
lookupIP = func(host string) ([]net.IP, error) { lookupIP = func(host string) ([]net.IP, error) {
@ -146,6 +144,52 @@ func makeURL(req string) string {
return testHTTPServer.URL + req return testHTTPServer.URL + req
} }
func makeHttpsURL(req string) string {
return testHTTPSServer.URL + req
}
func makeIndex(req string) *IndexInfo {
index := &IndexInfo{
Name: makeURL(req),
}
return index
}
func makeHttpsIndex(req string) *IndexInfo {
index := &IndexInfo{
Name: makeHttpsURL(req),
}
return index
}
func makePublicIndex() *IndexInfo {
index := &IndexInfo{
Name: IndexServerAddress(),
Secure: true,
Official: true,
}
return index
}
func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceConfig {
options := &Options{
Mirrors: opts.NewListOpts(nil),
InsecureRegistries: opts.NewListOpts(nil),
}
if mirrors != nil {
for _, mirror := range mirrors {
options.Mirrors.Set(mirror)
}
}
if insecure_registries != nil {
for _, insecure_registries := range insecure_registries {
options.InsecureRegistries.Set(insecure_registries)
}
}
return NewServiceConfig(options)
}
func writeHeaders(w http.ResponseWriter) { func writeHeaders(w http.ResponseWriter) {
h := w.Header() h := w.Header()
h.Add("Server", "docker-tests/mock") h.Add("Server", "docker-tests/mock")
@ -193,6 +237,40 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
t.Fatal(message) t.Fatal(message)
} }
func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
if a != b {
return
}
if len(message) == 0 {
message = fmt.Sprintf("%v == %v", a, b)
}
t.Fatal(message)
}
// Similar to assertEqual, but does not stop test
func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
if a == b {
return
}
message := fmt.Sprintf("%v != %v", a, b)
if len(messagePrefix) != 0 {
message = messagePrefix + ": " + message
}
t.Error(message)
}
// Similar to assertNotEqual, but does not stop test
func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
if a != b {
return
}
message := fmt.Sprintf("%v == %v", a, b)
if len(messagePrefix) != 0 {
message = messagePrefix + ": " + message
}
t.Error(message)
}
func requiresAuth(w http.ResponseWriter, r *http.Request) bool { func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
writeCookie := func() { writeCookie := func() {
value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
@ -271,6 +349,7 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
return return
} }
repositoryName := mux.Vars(r)["repository"] repositoryName := mux.Vars(r)["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName]
if !exists { if !exists {
apiError(w, "Repository not found", 404) apiError(w, "Repository not found", 404)
@ -290,6 +369,7 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
} }
vars := mux.Vars(r) vars := mux.Vars(r)
repositoryName := vars["repository"] repositoryName := vars["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"] tagName := vars["tag"]
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName]
if !exists { if !exists {
@ -310,6 +390,7 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
} }
vars := mux.Vars(r) vars := mux.Vars(r)
repositoryName := vars["repository"] repositoryName := vars["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"] tagName := vars["tag"]
tags, exists := testRepositories[repositoryName] tags, exists := testRepositories[repositoryName]
if !exists { if !exists {

View File

@ -21,7 +21,7 @@ const (
func spawnTestRegistrySession(t *testing.T) *Session { func spawnTestRegistrySession(t *testing.T) *Session {
authConfig := &AuthConfig{} authConfig := &AuthConfig{}
endpoint, err := NewEndpoint(makeURL("/v1/"), insecureRegistries) endpoint, err := NewEndpoint(makeIndex("/v1/"))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -32,8 +32,34 @@ func spawnTestRegistrySession(t *testing.T) *Session {
return r return r
} }
func TestPublicSession(t *testing.T) {
authConfig := &AuthConfig{}
getSessionDecorators := func(index *IndexInfo) int {
endpoint, err := NewEndpoint(index)
if err != nil {
t.Fatal(err)
}
r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
if err != nil {
t.Fatal(err)
}
return len(r.reqFactory.GetDecorators())
}
decorators := getSessionDecorators(makeIndex("/v1/"))
assertEqual(t, decorators, 0, "Expected no decorator on http session")
decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
assertNotEqual(t, decorators, 0, "Expected decorator on https session")
decorators = getSessionDecorators(makePublicIndex())
assertEqual(t, decorators, 0, "Expected no decorator on public session")
}
func TestPingRegistryEndpoint(t *testing.T) { func TestPingRegistryEndpoint(t *testing.T) {
ep, err := NewEndpoint(makeURL("/v1/"), insecureRegistries) testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
ep, err := NewEndpoint(index)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -41,7 +67,104 @@ func TestPingRegistryEndpoint(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
assertEqual(t, regInfo.Standalone, true, "Expected standalone to be true (default)")
assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
}
testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
testPing(makeHttpsIndex("/v1/"), true, "Expected standalone to be true (default)")
testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
}
func TestEndpoint(t *testing.T) {
// Simple wrapper to fail test if err != nil
expandEndpoint := func(index *IndexInfo) *Endpoint {
endpoint, err := NewEndpoint(index)
if err != nil {
t.Fatal(err)
}
return endpoint
}
assertInsecureIndex := func(index *IndexInfo) {
index.Secure = true
_, err := NewEndpoint(index)
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
index.Secure = false
}
assertSecureIndex := func(index *IndexInfo) {
index.Secure = true
_, err := NewEndpoint(index)
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
index.Secure = false
}
index := &IndexInfo{}
index.Name = makeURL("/v1/")
endpoint := expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
index.Name = makeURL("")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
httpURL := makeURL("")
index.Name = strings.SplitN(httpURL, "://", 2)[1]
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertInsecureIndex(index)
index.Name = makeHttpsURL("/v1/")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
index.Name = makeHttpsURL("")
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
httpsURL := makeHttpsURL("")
index.Name = strings.SplitN(httpsURL, "://", 2)[1]
endpoint = expandEndpoint(index)
assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
if endpoint.Version != APIVersion1 {
t.Fatal("Expected endpoint to be v1")
}
assertSecureIndex(index)
badEndpoints := []string{
"http://127.0.0.1/v1/",
"https://127.0.0.1/v1/",
"http://127.0.0.1",
"https://127.0.0.1",
"127.0.0.1",
}
for _, address := range badEndpoints {
index.Name = address
_, err := NewEndpoint(index)
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
}
} }
func TestGetRemoteHistory(t *testing.T) { func TestGetRemoteHistory(t *testing.T) {
@ -156,30 +279,413 @@ func TestPushImageLayerRegistry(t *testing.T) {
} }
} }
func TestResolveRepositoryName(t *testing.T) { func TestValidateRepositoryName(t *testing.T) {
_, _, err := ResolveRepositoryName("https://github.com/docker/docker") validRepoNames := []string{
assertEqual(t, err, ErrInvalidRepositoryName, "Expected error invalid repo name") "docker/docker",
ep, repo, err := ResolveRepositoryName("fooo/bar") "library/debian",
if err != nil { "debian",
t.Fatal(err) "docker.io/docker/docker",
"docker.io/library/debian",
"docker.io/debian",
"index.docker.io/docker/docker",
"index.docker.io/library/debian",
"index.docker.io/debian",
"127.0.0.1:5000/docker/docker",
"127.0.0.1:5000/library/debian",
"127.0.0.1:5000/debian",
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
}
invalidRepoNames := []string{
"https://github.com/docker/docker",
"docker/Docker",
"docker///docker",
"docker.io/docker/Docker",
"docker.io/docker///docker",
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
} }
assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address")
assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar")
u := makeURL("")[7:] for _, name := range invalidRepoNames {
ep, repo, err = ResolveRepositoryName(u + "/private/moonbase") err := ValidateRepositoryName(name)
if err != nil { assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
t.Fatal(err)
} }
assertEqual(t, ep, u, "Expected endpoint to be "+u)
assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase")
ep, repo, err = ResolveRepositoryName("ubuntu-12.04-base") for _, name := range validRepoNames {
err := ValidateRepositoryName(name)
assertEqual(t, err, nil, "Expected valid repo name: "+name)
}
err := ValidateRepositoryName(invalidRepoNames[0])
assertEqual(t, err, ErrInvalidRepositoryName, "Expected ErrInvalidRepositoryName: "+invalidRepoNames[0])
}
func TestParseRepositoryInfo(t *testing.T) {
expectedRepoInfos := map[string]RepositoryInfo{
"fooo/bar": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "fooo/bar",
LocalName: "fooo/bar",
CanonicalName: "fooo/bar",
Official: false,
},
"library/ubuntu": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "ubuntu",
Official: true,
},
"nonlibrary/ubuntu": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "nonlibrary/ubuntu",
LocalName: "nonlibrary/ubuntu",
CanonicalName: "nonlibrary/ubuntu",
Official: false,
},
"ubuntu": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu",
LocalName: "ubuntu",
CanonicalName: "ubuntu",
Official: true,
},
"other/library": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "other/library",
LocalName: "other/library",
CanonicalName: "other/library",
Official: false,
},
"127.0.0.1:8000/private/moonbase": {
Index: &IndexInfo{
Name: "127.0.0.1:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "127.0.0.1:8000/private/moonbase",
CanonicalName: "127.0.0.1:8000/private/moonbase",
Official: false,
},
"127.0.0.1:8000/privatebase": {
Index: &IndexInfo{
Name: "127.0.0.1:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "127.0.0.1:8000/privatebase",
CanonicalName: "127.0.0.1:8000/privatebase",
Official: false,
},
"localhost:8000/private/moonbase": {
Index: &IndexInfo{
Name: "localhost:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost:8000/private/moonbase",
CanonicalName: "localhost:8000/private/moonbase",
Official: false,
},
"localhost:8000/privatebase": {
Index: &IndexInfo{
Name: "localhost:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost:8000/privatebase",
CanonicalName: "localhost:8000/privatebase",
Official: false,
},
"example.com/private/moonbase": {
Index: &IndexInfo{
Name: "example.com",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com/private/moonbase",
CanonicalName: "example.com/private/moonbase",
Official: false,
},
"example.com/privatebase": {
Index: &IndexInfo{
Name: "example.com",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com/privatebase",
CanonicalName: "example.com/privatebase",
Official: false,
},
"example.com:8000/private/moonbase": {
Index: &IndexInfo{
Name: "example.com:8000",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "example.com:8000/private/moonbase",
CanonicalName: "example.com:8000/private/moonbase",
Official: false,
},
"example.com:8000/privatebase": {
Index: &IndexInfo{
Name: "example.com:8000",
Official: false,
},
RemoteName: "privatebase",
LocalName: "example.com:8000/privatebase",
CanonicalName: "example.com:8000/privatebase",
Official: false,
},
"localhost/private/moonbase": {
Index: &IndexInfo{
Name: "localhost",
Official: false,
},
RemoteName: "private/moonbase",
LocalName: "localhost/private/moonbase",
CanonicalName: "localhost/private/moonbase",
Official: false,
},
"localhost/privatebase": {
Index: &IndexInfo{
Name: "localhost",
Official: false,
},
RemoteName: "privatebase",
LocalName: "localhost/privatebase",
CanonicalName: "localhost/privatebase",
Official: false,
},
IndexServerName() + "/public/moonbase": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "public/moonbase",
Official: false,
},
"index." + IndexServerName() + "/public/moonbase": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "public/moonbase",
Official: false,
},
IndexServerName() + "/public/moonbase": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "public/moonbase",
LocalName: "public/moonbase",
CanonicalName: "public/moonbase",
Official: false,
},
"ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "ubuntu-12.04-base",
Official: true,
},
IndexServerName() + "/ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "ubuntu-12.04-base",
Official: true,
},
IndexServerName() + "/ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "ubuntu-12.04-base",
Official: true,
},
"index." + IndexServerName() + "/ubuntu-12.04-base": {
Index: &IndexInfo{
Name: IndexServerName(),
Official: true,
},
RemoteName: "library/ubuntu-12.04-base",
LocalName: "ubuntu-12.04-base",
CanonicalName: "ubuntu-12.04-base",
Official: true,
},
}
for reposName, expectedRepoInfo := range expectedRepoInfos {
repoInfo, err := ParseRepositoryInfo(reposName)
if err != nil {
t.Error(err)
} else {
checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
checkEqual(t, repoInfo.RemoteName, expectedRepoInfo.RemoteName, reposName)
checkEqual(t, repoInfo.LocalName, expectedRepoInfo.LocalName, reposName)
checkEqual(t, repoInfo.CanonicalName, expectedRepoInfo.CanonicalName, reposName)
checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
}
}
}
func TestNewIndexInfo(t *testing.T) {
testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
for indexName, expectedIndexInfo := range expectedIndexInfos {
index, err := config.NewIndexInfo(indexName)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} else {
checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
} }
assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be "+IndexServerAddress()) }
assertEqual(t, repo, "ubuntu-12.04-base", "Expected endpoint to be ubuntu-12.04-base") }
config := NewServiceConfig(nil)
noMirrors := make([]string, 0)
expectedIndexInfos := map[string]*IndexInfo{
IndexServerName(): {
Name: IndexServerName(),
Official: true,
Secure: true,
Mirrors: noMirrors,
},
"index." + IndexServerName(): {
Name: IndexServerName(),
Official: true,
Secure: true,
Mirrors: noMirrors,
},
"example.com": {
Name: "example.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"}
config = makeServiceConfig(publicMirrors, []string{"example.com"})
expectedIndexInfos = map[string]*IndexInfo{
IndexServerName(): {
Name: IndexServerName(),
Official: true,
Secure: true,
Mirrors: publicMirrors,
},
"index." + IndexServerName(): {
Name: IndexServerName(),
Official: true,
Secure: true,
Mirrors: publicMirrors,
},
"example.com": {
Name: "example.com",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"example.com:5000": {
Name: "example.com:5000",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
"127.0.0.1": {
Name: "127.0.0.1",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"other.com": {
Name: "other.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
config = makeServiceConfig(nil, []string{"42.42.0.0/16"})
expectedIndexInfos = map[string]*IndexInfo{
"example.com": {
Name: "example.com",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"example.com:5000": {
Name: "example.com:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1": {
Name: "127.0.0.1",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"127.0.0.1:5000": {
Name: "127.0.0.1:5000",
Official: false,
Secure: false,
Mirrors: noMirrors,
},
"other.com": {
Name: "other.com",
Official: false,
Secure: true,
Mirrors: noMirrors,
},
}
testIndexInfo(config, expectedIndexInfos)
} }
func TestPushRegistryTag(t *testing.T) { func TestPushRegistryTag(t *testing.T) {
@ -232,7 +738,7 @@ func TestSearchRepositories(t *testing.T) {
assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars") assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars")
} }
func TestValidRepositoryName(t *testing.T) { func TestValidRemoteName(t *testing.T) {
validRepositoryNames := []string{ validRepositoryNames := []string{
// Sanity check. // Sanity check.
"docker/docker", "docker/docker",
@ -247,7 +753,7 @@ func TestValidRepositoryName(t *testing.T) {
"____/____", "____/____",
} }
for _, repositoryName := range validRepositoryNames { for _, repositoryName := range validRepositoryNames {
if err := validateRepositoryName(repositoryName); err != nil { if err := validateRemoteName(repositoryName); err != nil {
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
} }
} }
@ -277,7 +783,7 @@ func TestValidRepositoryName(t *testing.T) {
"docker/", "docker/",
} }
for _, repositoryName := range invalidRepositoryNames { for _, repositoryName := range invalidRepositoryNames {
if err := validateRepositoryName(repositoryName); err == nil { if err := validateRemoteName(repositoryName); err == nil {
t.Errorf("Repository name should be invalid: %v", repositoryName) t.Errorf("Repository name should be invalid: %v", repositoryName)
} }
} }
@ -350,13 +856,13 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
} }
} }
func TestIsSecure(t *testing.T) { func TestIsSecureIndex(t *testing.T) {
tests := []struct { tests := []struct {
addr string addr string
insecureRegistries []string insecureRegistries []string
expected bool expected bool
}{ }{
{IndexServerURL.Host, nil, true}, {IndexServerName(), nil, true},
{"example.com", []string{}, true}, {"example.com", []string{}, true},
{"example.com", []string{"example.com"}, false}, {"example.com", []string{"example.com"}, false},
{"localhost", []string{"localhost:5000"}, false}, {"localhost", []string{"localhost:5000"}, false},
@ -383,10 +889,9 @@ func TestIsSecure(t *testing.T) {
{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false}, {"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false},
} }
for _, tt := range tests { for _, tt := range tests {
// TODO: remove this once we remove localhost insecure by default config := makeServiceConfig(nil, tt.insecureRegistries)
insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8") if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected { t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
} }
} }
} }

View File

@ -13,14 +13,14 @@ import (
// 'pull': Download images from any registry (TODO) // 'pull': Download images from any registry (TODO)
// 'push': Upload images to any registry (TODO) // 'push': Upload images to any registry (TODO)
type Service struct { type Service struct {
insecureRegistries []string Config *ServiceConfig
} }
// NewService returns a new instance of Service ready to be // NewService returns a new instance of Service ready to be
// installed no an engine. // installed no an engine.
func NewService(insecureRegistries []string) *Service { func NewService(options *Options) *Service {
return &Service{ return &Service{
insecureRegistries: insecureRegistries, Config: NewServiceConfig(options),
} }
} }
@ -28,6 +28,9 @@ func NewService(insecureRegistries []string) *Service {
func (s *Service) Install(eng *engine.Engine) error { func (s *Service) Install(eng *engine.Engine) error {
eng.Register("auth", s.Auth) eng.Register("auth", s.Auth)
eng.Register("search", s.Search) eng.Register("search", s.Search)
eng.Register("resolve_repository", s.ResolveRepository)
eng.Register("resolve_index", s.ResolveIndex)
eng.Register("registry_config", s.GetRegistryConfig)
return nil return nil
} }
@ -39,16 +42,19 @@ func (s *Service) Auth(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig) job.GetenvJson("authConfig", authConfig)
if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() { if authConfig.ServerAddress != "" {
endpoint, err := NewEndpoint(addr, s.insecureRegistries) index, err := ResolveIndexInfo(job, authConfig.ServerAddress)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
if _, err := endpoint.Ping(); err != nil { if !index.Official {
endpoint, err := NewEndpoint(index)
if err != nil {
return job.Error(err) return job.Error(err)
} }
authConfig.ServerAddress = endpoint.String() authConfig.ServerAddress = endpoint.String()
} }
}
status, err := Login(authConfig, HTTPRequestFactory(nil)) status, err := Login(authConfig, HTTPRequestFactory(nil))
if err != nil { if err != nil {
@ -87,12 +93,12 @@ func (s *Service) Search(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig) job.GetenvJson("authConfig", authConfig)
job.GetenvJson("metaHeaders", metaHeaders) job.GetenvJson("metaHeaders", metaHeaders)
hostname, term, err := ResolveRepositoryName(term) repoInfo, err := ResolveRepositoryInfo(job, term)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
// *TODO: Search multiple indexes.
endpoint, err := NewEndpoint(hostname, s.insecureRegistries) endpoint, err := repoInfo.GetEndpoint()
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
@ -100,7 +106,7 @@ func (s *Service) Search(job *engine.Job) engine.Status {
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
results, err := r.SearchRepositories(term) results, err := r.SearchRepositories(repoInfo.GetSearchTerm())
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
@ -116,3 +122,92 @@ func (s *Service) Search(job *engine.Job) engine.Status {
} }
return engine.StatusOK return engine.StatusOK
} }
// ResolveRepository splits a repository name into its components
// and configuration of the associated registry.
func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
var (
reposName = job.Args[0]
)
repoInfo, err := s.Config.NewRepositoryInfo(reposName)
if err != nil {
return job.Error(err)
}
out := engine.Env{}
err = out.SetJson("repository", repoInfo)
if err != nil {
return job.Error(err)
}
out.WriteTo(job.Stdout)
return engine.StatusOK
}
// Convenience wrapper for calling resolve_repository Job from a running job.
func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*RepositoryInfo, error) {
job := jobContext.Eng.Job("resolve_repository", reposName)
env, err := job.Stdout.AddEnv()
if err != nil {
return nil, err
}
if err := job.Run(); err != nil {
return nil, err
}
info := RepositoryInfo{}
if err := env.GetJson("repository", &info); err != nil {
return nil, err
}
return &info, nil
}
// ResolveIndex takes indexName and returns index info
func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
var (
indexName = job.Args[0]
)
index, err := s.Config.NewIndexInfo(indexName)
if err != nil {
return job.Error(err)
}
out := engine.Env{}
err = out.SetJson("index", index)
if err != nil {
return job.Error(err)
}
out.WriteTo(job.Stdout)
return engine.StatusOK
}
// Convenience wrapper for calling resolve_index Job from a running job.
func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, error) {
job := jobContext.Eng.Job("resolve_index", indexName)
env, err := job.Stdout.AddEnv()
if err != nil {
return nil, err
}
if err := job.Run(); err != nil {
return nil, err
}
info := IndexInfo{}
if err := env.GetJson("index", &info); err != nil {
return nil, err
}
return &info, nil
}
// GetRegistryConfig returns current registry configuration.
func (s *Service) GetRegistryConfig(job *engine.Job) engine.Status {
out := engine.Env{}
err := out.SetJson("config", s.Config)
if err != nil {
return job.Error(err)
}
out.WriteTo(job.Stdout)
return engine.StatusOK
}

View File

@ -65,3 +65,44 @@ const (
APIVersion1 = iota + 1 APIVersion1 = iota + 1
APIVersion2 APIVersion2
) )
// RepositoryInfo Examples:
// {
// "Index" : {
// "Name" : "docker.io",
// "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"],
// "Secure" : true,
// "Official" : true,
// },
// "RemoteName" : "library/debian",
// "LocalName" : "debian",
// "CanonicalName" : "docker.io/debian"
// "Official" : true,
// }
// {
// "Index" : {
// "Name" : "127.0.0.1:5000",
// "Mirrors" : [],
// "Secure" : false,
// "Official" : false,
// },
// "RemoteName" : "user/repo",
// "LocalName" : "127.0.0.1:5000/user/repo",
// "CanonicalName" : "127.0.0.1:5000/user/repo",
// "Official" : false,
// }
type IndexInfo struct {
Name string
Mirrors []string
Secure bool
Official bool
}
type RepositoryInfo struct {
Index *IndexInfo
RemoteName string
LocalName string
CanonicalName string
Official bool
}

View File

@ -134,6 +134,10 @@ func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) {
self.decorators = append(self.decorators, d...) self.decorators = append(self.decorators, d...)
} }
func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator {
return self.decorators
}
// NewRequest() creates a new *http.Request, // NewRequest() creates a new *http.Request,
// applies all decorators in the HTTPRequestFactory on the request, // applies all decorators in the HTTPRequestFactory on the request,
// then applies decorators provided by d on the request. // then applies decorators provided by d on the request.