1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

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
if *tag != "" {
repository, tag := parsers.ParseRepositoryTag(*tag)
if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
if err := registry.ValidateRepositoryName(repository); err != nil {
return err
}
if len(tag) > 0 {
@ -1148,7 +1148,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
if repository != "" {
//Check if the given image name can be resolved
repo, _ := parsers.ParseRepositoryTag(repository)
if _, _, err := registry.ResolveRepositoryName(repo); err != nil {
if err := registry.ValidateRepositoryName(repo); err != nil {
return err
}
}
@ -1174,23 +1174,23 @@ func (cli *DockerCli) CmdPush(args ...string) error {
remote, tag := parsers.ParseRepositoryTag(name)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(remote)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(remote)
if err != nil {
return err
}
// 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
// applied to repository names and can warn the user in advance.
// Custom repositories can have different rules, and we must also
// allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 {
username := cli.configFile.Configs[registry.IndexServerAddress()].Username
if repoInfo.Official {
username := authConfig.Username
if username == "" {
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{}
@ -1212,10 +1212,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
if err := push(authConfig); err != nil {
if strings.Contains(err.Error(), "Status 401") {
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
}
authConfig := cli.configFile.ResolveAuthConfig(hostname)
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
return push(authConfig)
}
return err
@ -1245,8 +1245,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
v.Set("fromImage", newRemote)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(taglessRemote)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
if err != nil {
return err
}
@ -1254,7 +1254,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
cli.LoadConfigFile()
// 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 {
buf, err := json.Marshal(authConfig)
@ -1273,10 +1273,10 @@ func (cli *DockerCli) CmdPull(args ...string) error {
if err := pull(authConfig); err != nil {
if strings.Contains(err.Error(), "Status 401") {
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
}
authConfig := cli.configFile.ResolveAuthConfig(hostname)
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
return pull(authConfig)
}
return err
@ -1691,7 +1691,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
//Check if the given image name can be resolved
if repository != "" {
if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
if err := registry.ValidateRepositoryName(repository); err != nil {
return err
}
}
@ -2002,7 +2002,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
)
//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
}
v.Set("repo", repository)
@ -2032,8 +2032,8 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
v.Set("fromImage", repos)
v.Set("tag", tag)
// Resolve the Repository name from fqn to hostname + name
hostname, _, err := registry.ResolveRepositoryName(repos)
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(repos)
if err != nil {
return err
}
@ -2042,7 +2042,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
cli.LoadConfigFile()
// Resolve the Auth config relevant for this server
authConfig := cli.configFile.ResolveAuthConfig(hostname)
authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index)
buf, err := json.Marshal(authConfig)
if err != nil {
return err

View file

@ -66,7 +66,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
if passAuthInfo {
cli.LoadConfigFile()
// 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) {
buf, err := json.Marshal(authConfig)
if err != nil {
@ -260,7 +260,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
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)
}
}()

View file

@ -427,17 +427,17 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
if tag == "" {
tag = "latest"
}
job := b.Engine.Job("pull", remote, tag)
pullRegistryAuth := b.AuthConfig
if len(b.AuthConfigFile.Configs) > 0 {
// 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 {
return nil, err
}
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint)
resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index)
pullRegistryAuth = &resolvedAuth
}
job := b.Engine.Job("pull", remote, tag)
job.SetenvBool("json", b.StreamFormatter.Json())
job.SetenvBool("parallel", true)
job.SetenvJson("authConfig", pullRegistryAuth)

View file

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

View file

@ -23,7 +23,6 @@ type Config struct {
AutoRestart bool
Dns []string
DnsSearch []string
Mirrors []string
EnableIptables bool
EnableIpForward bool
EnableIpMasq bool
@ -31,7 +30,6 @@ type Config struct {
BridgeIface string
BridgeIP string
FixedCIDR string
InsecureRegistries []string
InterContainerCommunication bool
GraphDriver 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.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)")
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.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")
@ -69,16 +66,7 @@ func (config *Config) InstallFlags() {
// FIXME: why the inconsistency between "hosts" and "sockets"?
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.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`)")
// 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 {

View file

@ -898,7 +898,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
}
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 {
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 {
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.SetJson("ID", daemon.ID)
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("OperatingSystem", operatingSystem)
v.Set("IndexServerAddress", registry.IndexServerAddress())
v.SetJson("RegistryConfig", registryConfig)
v.Set("InitSha1", dockerversion.INITSHA1)
v.Set("InitPath", initPath)
v.SetInt("NCPU", runtime.NumCPU())

View file

@ -19,11 +19,13 @@ import (
const CanDaemon = true
var (
daemonCfg = &daemon.Config{}
daemonCfg = &daemon.Config{}
registryCfg = &registry.Options{}
)
func init() {
daemonCfg.InstallFlags()
registryCfg.InstallFlags()
}
func mainDaemon() {
@ -42,7 +44,7 @@ func mainDaemon() {
}
// 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)
}

View file

@ -11,6 +11,7 @@ import (
"github.com/docker/docker/engine"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
)
// 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 {
name = registry.NormalizeLocalName(name)
log.Debugf("Serializing %s", name)
rootRepo := s.Repositories[name]
if rootRepo != nil {

View file

@ -85,9 +85,14 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
sf = utils.NewStreamFormatter(job.GetenvBool("json"))
authConfig = &registry.AuthConfig{}
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 {
tag = job.Args[1]
}
@ -95,25 +100,19 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig)
job.GetenvJson("metaHeaders", &metaHeaders)
c, err := s.poolAdd("pull", localName+":"+tag)
c, err := s.poolAdd("pull", repoInfo.LocalName+":"+tag)
if err != nil {
if c != nil {
// 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
return engine.StatusOK
}
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
hostname, remoteName, err := registry.ResolveRepositoryName(localName)
if err != nil {
return job.Error(err)
}
endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries)
endpoint, err := repoInfo.GetEndpoint()
if err != nil {
return job.Error(err)
}
@ -123,32 +122,18 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
return job.Error(err)
}
var isOfficial bool
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
logName := repoInfo.LocalName
if 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")
if err = j.Run(); err != nil {
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 {
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)
}
@ -169,20 +154,20 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
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 {
out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
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", repoInfo.CanonicalName))
repoData, err := r.GetRepositoryData(remoteName)
repoData, err := r.GetRepositoryData(repoInfo.RemoteName)
if err != nil {
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
return err
}
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 {
log.Errorf("%v", 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
id, exists := tagsList[askedTag]
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
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)
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
var lastErr, err error
var is_downloaded bool
if mirrors != nil {
for _, ep := range mirrors {
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 {
// Don't report errors when pulling from mirrors.
log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, localName, ep, err)
continue
}
layers_downloaded = layers_downloaded || is_downloaded
success = true
break
for _, ep := range repoInfo.Index.Mirrors {
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), 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.
log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err)
continue
}
layers_downloaded = layers_downloaded || is_downloaded
success = true
break
}
if !success {
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 {
// 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.
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
}
layers_downloaded = layers_downloaded || is_downloaded
@ -283,7 +266,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
}
}
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))
if parallel {
errors <- err
@ -319,14 +302,14 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName,
if askedTag != "" && id != imageId {
continue
}
if err := s.Set(localName, tag, id, true); err != nil {
if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil {
return err
}
}
requestedTag := localName
requestedTag := repoInfo.CanonicalName
if len(askedTag) > 0 {
requestedTag = localName + ":" + askedTag
requestedTag = repoInfo.CanonicalName + ":" + askedTag
}
WriteStatus(requestedTag, out, sf, layers_downloaded)
return nil
@ -440,40 +423,40 @@ type downloadInfo struct {
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
if tag == "" {
log.Debugf("Pulling tag list from V2 registry for %s", remoteName)
tags, err := r.GetV2RemoteTags(remoteName, nil)
log.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName)
tags, err := r.GetV2RemoteTags(repoInfo.RemoteName, nil)
if err != nil {
return err
}
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
} else if downloaded {
layersDownloaded = true
}
}
} 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
} else if downloaded {
layersDownloaded = true
}
}
requestedTag := localName
requestedTag := repoInfo.CanonicalName
if len(tag) > 0 {
requestedTag = localName + ":" + tag
requestedTag = repoInfo.CanonicalName + ":" + tag
}
WriteStatus(requestedTag, out, sf, layersDownloaded)
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)
manifestBytes, err := r.GetV2ImageManifest(remoteName, tag, nil)
manifestBytes, err := r.GetV2ImageManifest(repoInfo.RemoteName, tag, nil)
if err != nil {
return false, err
}
@ -488,9 +471,9 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
}
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 {
out.Write(sf.FormatStatus(tag, "Pulling from %s", localName))
out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName))
}
if len(manifest.FSLayers) == 0 {
@ -542,7 +525,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
return err
}
r, l, err := r.GetV2ImageBlobReader(remoteName, sumType, checksum, nil)
r, l, err := r.GetV2ImageBlobReader(repoInfo.RemoteName, sumType, checksum, nil)
if err != nil {
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
}

View file

@ -61,7 +61,7 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string
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)
log.Debugf("Local repo: %s", localRepo)
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
// 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 {
return err
}
@ -114,11 +114,11 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName,
nTag = len(localRepo)
}
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 {
if err := r.LookupRemoteImage(imgId, ep, repoData.Tokens); err != nil {
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?
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)))
}
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
}
}
}
}
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 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)
jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
if err != nil {
@ -199,26 +199,27 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
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")
job.GetenvJson("authConfig", authConfig)
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)
}
defer s.poolRemove("push", localName)
defer s.poolRemove("push", repoInfo.LocalName)
// Resolve the Repository name from fqn to endpoint + name
hostname, remoteName, err := registry.ResolveRepositoryName(localName)
endpoint, err := repoInfo.GetEndpoint()
if err != nil {
return job.Error(err)
}
endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries)
if err != nil {
return job.Error(err)
}
img, err := s.graph.Get(localName)
img, err := s.graph.Get(repoInfo.LocalName)
r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
if err2 != nil {
return job.Error(err2)
@ -227,12 +228,12 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
if err != nil {
reposLen := 1
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 localRepo, exists := s.Repositories[localName]; exists {
if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
if localRepo, exists := s.Repositories[repoInfo.LocalName]; exists {
if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil {
return job.Error(err)
}
return engine.StatusOK
@ -241,8 +242,8 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
}
var token []string
job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint.String(), token, sf); err != nil {
job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", repoInfo.CanonicalName))
if _, err := s.pushImage(r, job.Stdout, img.ID, endpoint.String(), token, sf); err != nil {
return job.Error(err)
}
return engine.StatusOK

View file

@ -13,6 +13,7 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
@ -23,11 +24,9 @@ var (
)
type TagStore struct {
path string
graph *Graph
mirrors []string
insecureRegistries []string
Repositories map[string]Repository
path string
graph *Graph
Repositories map[string]Repository
sync.Mutex
// FIXME: move push/pull-related fields
// to a helper type
@ -55,20 +54,18 @@ func (r Repository) Contains(u Repository) bool {
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)
if err != nil {
return nil, err
}
store := &TagStore{
path: abspath,
graph: graph,
mirrors: mirrors,
insecureRegistries: insecureRegistries,
Repositories: make(map[string]Repository),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}),
path: abspath,
graph: graph,
Repositories: make(map[string]Repository),
pullingPool: make(map[string]chan struct{}),
pushingPool: make(map[string]chan struct{}),
}
// Load the json file if it exists, otherwise create it.
if err := store.reload(); os.IsNotExist(err) {
@ -178,6 +175,7 @@ func (store *TagStore) Delete(repoName, tag string) (bool, error) {
if err := store.reload(); err != nil {
return false, err
}
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
if tag != "" {
if _, exists2 := r[tag]; exists2 {
@ -219,6 +217,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err
}
var repo Repository
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
repo = r
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 {
return nil, err
}
repoName = registry.NormalizeLocalName(repoName)
if r, exists := store.Repositories[repoName]; exists {
return r, nil
}
@ -279,20 +279,6 @@ func (store *TagStore) GetRepoRefs() map[string][]string {
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
func validateRepoName(name string) error {
if name == "" {

View file

@ -15,8 +15,12 @@ import (
)
const (
testImageName = "myapp"
testImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234"
testOfficialImageName = "myapp"
testOfficialImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234"
testOfficialImageIDShort = "1a2d3c4d4e5f"
testPrivateImageName = "127.0.0.1:8000/privateapp"
testPrivateImageID = "5bc255f8699e4ee89ac4469266c3d11515da88fdcbde45d7b069b636ff4efd81"
testPrivateImageIDShort = "5bc255f8699e"
)
func fakeTar() (io.Reader, error) {
@ -53,19 +57,30 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil {
t.Fatal(err)
}
store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil)
store, err := NewTagStore(path.Join(root, "tags"), graph)
if err != nil {
t.Fatal(err)
}
archive, err := fakeTar()
officialArchive, err := fakeTar()
if err != nil {
t.Fatal(err)
}
img := &image.Image{ID: testImageID}
if err := graph.Register(img, archive); err != nil {
img := &image.Image{ID: testOfficialImageID}
if err := graph.Register(img, officialArchive); err != nil {
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)
}
return store
@ -80,39 +95,65 @@ func TestLookupImage(t *testing.T) {
store := mkTestTagStore(tmp, t)
defer store.graph.driver.Cleanup()
if img, err := store.LookupImage(testImageName); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
}
if img, err := store.LookupImage(testImageName + ":" + DEFAULTTAG); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
officialLookups := []string{
testOfficialImageID,
testOfficialImageIDShort,
testOfficialImageName + ":" + testOfficialImageID,
testOfficialImageName + ":" + testOfficialImageIDShort,
testOfficialImageName,
testOfficialImageName + ":" + DEFAULTTAG,
"docker.io/" + testOfficialImageName,
"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 {
t.Errorf("Expected error, none found")
} else if img != nil {
t.Errorf("Expected 0 image, 1 found")
privateLookups := []string{
testPrivateImageID,
testPrivateImageIDShort,
testPrivateImageName + ":" + testPrivateImageID,
testPrivateImageName + ":" + testPrivateImageIDShort,
testPrivateImageName,
testPrivateImageName + ":" + DEFAULTTAG,
}
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")
invalidLookups := []string{
testOfficialImageName + ":" + "fail",
"fail:fail",
}
if img, err := store.LookupImage(testImageID); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
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)
}
}
if img, err := store.LookupImage(testImageName + ":" + testImageID); err != nil {
t.Fatal(err)
} else if img == nil {
t.Errorf("Expected 1 image, none found")
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 {
t.Errorf("Expected 0 image, 1 found: %s", name)
}
}
}
@ -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")
}
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 (
"os/exec"
"strings"
"testing"
)
@ -24,3 +25,33 @@ func TestPullNonExistingImage(t *testing.T) {
}
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")
}
// 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/engine"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
)
@ -173,7 +174,14 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
eng := engine.New()
eng.Logging = false
// 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)
cfg := &daemon.Config{
Root: root,

View file

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

View file

@ -30,7 +30,23 @@ func TestValidateIPAddress(t *testing.T) {
func TestListOpts(t *testing.T) {
o := NewListOpts(nil)
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) {

View file

@ -7,7 +7,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strings"
@ -18,27 +17,12 @@ import (
const (
// Where we store the config file
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 (
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 {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
@ -52,10 +36,6 @@ type ConfigFile struct {
rootPath string
}
func IndexServerAddress() string {
return INDEXSERVER
}
// create a base64 encoded auth string to store in config
func encodeAuth(authConfig *AuthConfig) string {
authStr := authConfig.Username + ":" + authConfig.Password
@ -118,6 +98,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
}
authConfig.Email = origEmail[1]
authConfig.ServerAddress = IndexServerAddress()
// *TODO: Switch to using IndexServerName() instead?
configFile.Configs[IndexServerAddress()] = authConfig
} else {
for k, authConfig := range configFile.Configs {
@ -181,7 +162,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
)
if serverAddress == "" {
serverAddress = IndexServerAddress()
return "", fmt.Errorf("Server Error: Server Address not set.")
}
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" +
" to your e-mail to activate it."
} 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."
}
} else if reqStatusCode == 400 {
@ -236,6 +218,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
if loginAgainstOfficialIndex {
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: %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
func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
if hostname == IndexServerAddress() || len(hostname) == 0 {
// default to the index server
return config.Configs[IndexServerAddress()]
}
func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
configKey := index.GetAuthConfigKey()
// First try the happy case
if c, found := config.Configs[hostname]; found {
if c, found := config.Configs[configKey]; found || index.Official {
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
// them to the new format and testing
normalizedHostename := convertToHostname(hostname)
for registry, config := range config.Configs {
if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename {
if configKey == convertToHostname(registry) {
return config
}
}

View file

@ -81,12 +81,20 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
}
defer os.RemoveAll(configFile.rootPath)
for _, registry := range []string{"", IndexServerAddress()} {
resolved := configFile.ResolveAuthConfig(registry)
if resolved != configFile.Configs[IndexServerAddress()] {
t.Fail()
}
indexConfig := configFile.Configs[IndexServerAddress()]
officialIndex := &IndexInfo{
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) {
@ -106,18 +114,27 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
Password: "bar-pass",
Email: "bar@example.com",
}
configFile.Configs["https://registry.example.com/v1/"] = registryAuth
configFile.Configs["http://localhost:8000/v1/"] = localAuth
configFile.Configs["registry.com"] = registryAuth
officialAuth := AuthConfig{
Username: "baz-user",
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{
"https://registry.example.com/v1/": {
"registry.example.com": {
"https://registry.example.com/v1/",
"http://registry.example.com/v1/",
"registry.example.com",
"registry.example.com/v1/",
},
"http://localhost:8000/v1/": {
"localhost:8000": {
"https://localhost:8000/v1/",
"http://localhost:8000/v1/",
"localhost:8000",
@ -132,18 +149,24 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
}
for configKey, registries := range validRegistries {
configured, ok := expectedAuths[configKey]
if !ok || configured.Email == "" {
t.Fatal()
}
index := &IndexInfo{
Name: configKey,
}
for _, registry := range registries {
var (
configured AuthConfig
ok bool
)
resolved := configFile.ResolveAuthConfig(registry)
if configured, ok = configFile.Configs[configKey]; !ok {
t.Fail()
}
configFile.Configs[registry] = configured
resolved := configFile.ResolveAuthConfig(index)
if 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
}
func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname, insecureRegistries)
func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints.
endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
if err != nil {
return nil, err
}
@ -49,7 +50,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
//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`
// 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)
@ -68,7 +69,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
return endpoint, nil
}
func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var (
endpoint = Endpoint{}
trimmedHostname string
@ -82,13 +83,14 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
if err != nil {
return nil, err
}
endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries)
if err != nil {
return nil, err
}
endpoint.secure = secure
return &endpoint, nil
}
func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
return NewEndpoint(repoInfo.Index)
}
type Endpoint struct {
URL *url.URL
Version APIVersion
@ -155,63 +157,3 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
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/"},
}
for _, td := range testData {
e, err := newEndpoint(td.str, insecureRegistries)
e, err := newEndpoint(td.str, false)
if err != nil {
t.Errorf("%q: %s", td.str, err)
}

View file

@ -10,7 +10,6 @@ import (
"net/http"
"os"
"path"
"regexp"
"strings"
"time"
@ -19,12 +18,9 @@ import (
)
var (
ErrAlreadyExists = errors.New("Image already exists")
ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
ErrDoesNotExist = errors.New("Image does not exist")
errLoginRequired = errors.New("Authentication is required.")
validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`)
validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
ErrAlreadyExists = errors.New("Image already exists")
ErrDoesNotExist = errors.New("Image does not exist")
errLoginRequired = errors.New("Authentication is required.")
)
type TimeoutType uint32
@ -160,67 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
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 {
var (
trusteds = []string{"docker.com", "docker.io"}

View file

@ -15,15 +15,16 @@ import (
"testing"
"time"
"github.com/docker/docker/opts"
"github.com/gorilla/mux"
log "github.com/Sirupsen/logrus"
)
var (
testHTTPServer *httptest.Server
insecureRegistries []string
testLayers = map[string]map[string]string{
testHTTPServer *httptest.Server
testHTTPSServer *httptest.Server
testLayers = map[string]map[string]string{
"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
@ -86,6 +87,7 @@ var (
"": {net.ParseIP("0.0.0.0")},
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
"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")
testHTTPServer = httptest.NewServer(handlerAccessLog(r))
URL, err := url.Parse(testHTTPServer.URL)
if err != nil {
panic(err)
}
insecureRegistries = []string{URL.Host}
testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r))
// override net.LookupIP
lookupIP = func(host string) ([]net.IP, error) {
@ -146,6 +144,52 @@ func makeURL(req string) string {
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) {
h := w.Header()
h.Add("Server", "docker-tests/mock")
@ -193,6 +237,40 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
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 {
writeCookie := func() {
value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
@ -271,6 +349,7 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
return
}
repositoryName := mux.Vars(r)["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tags, exists := testRepositories[repositoryName]
if !exists {
apiError(w, "Repository not found", 404)
@ -290,6 +369,7 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
repositoryName := vars["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"]
tags, exists := testRepositories[repositoryName]
if !exists {
@ -310,6 +390,7 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
repositoryName := vars["repository"]
repositoryName = NormalizeLocalName(repositoryName)
tagName := vars["tag"]
tags, exists := testRepositories[repositoryName]
if !exists {

View file

@ -21,7 +21,7 @@ const (
func spawnTestRegistrySession(t *testing.T) *Session {
authConfig := &AuthConfig{}
endpoint, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
endpoint, err := NewEndpoint(makeIndex("/v1/"))
if err != nil {
t.Fatal(err)
}
@ -32,16 +32,139 @@ func spawnTestRegistrySession(t *testing.T) *Session {
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) {
ep, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
if err != nil {
t.Fatal(err)
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
ep, err := NewEndpoint(index)
if err != nil {
t.Fatal(err)
}
regInfo, err := ep.Ping()
if err != nil {
t.Fatal(err)
}
assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
}
regInfo, err := ep.Ping()
if err != nil {
t.Fatal(err)
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")
}
assertEqual(t, regInfo.Standalone, true, "Expected standalone to be true (default)")
}
func TestGetRemoteHistory(t *testing.T) {
@ -156,30 +279,413 @@ func TestPushImageLayerRegistry(t *testing.T) {
}
}
func TestResolveRepositoryName(t *testing.T) {
_, _, err := ResolveRepositoryName("https://github.com/docker/docker")
assertEqual(t, err, ErrInvalidRepositoryName, "Expected error invalid repo name")
ep, repo, err := ResolveRepositoryName("fooo/bar")
if err != nil {
t.Fatal(err)
func TestValidateRepositoryName(t *testing.T) {
validRepoNames := []string{
"docker/docker",
"library/debian",
"debian",
"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:]
ep, repo, err = ResolveRepositoryName(u + "/private/moonbase")
if err != nil {
t.Fatal(err)
for _, name := range invalidRepoNames {
err := ValidateRepositoryName(name)
assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
}
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")
if err != nil {
t.Fatal(err)
for _, name := range validRepoNames {
err := ValidateRepositoryName(name)
assertEqual(t, err, nil, "Expected valid repo name: "+name)
}
assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be "+IndexServerAddress())
assertEqual(t, repo, "ubuntu-12.04-base", "Expected endpoint to be ubuntu-12.04-base")
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 {
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")
}
}
}
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) {
@ -232,7 +738,7 @@ func TestSearchRepositories(t *testing.T) {
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{
// Sanity check.
"docker/docker",
@ -247,7 +753,7 @@ func TestValidRepositoryName(t *testing.T) {
"____/____",
}
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)
}
}
@ -277,7 +783,7 @@ func TestValidRepositoryName(t *testing.T) {
"docker/",
}
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)
}
}
@ -350,13 +856,13 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
}
}
func TestIsSecure(t *testing.T) {
func TestIsSecureIndex(t *testing.T) {
tests := []struct {
addr string
insecureRegistries []string
expected bool
}{
{IndexServerURL.Host, nil, true},
{IndexServerName(), nil, true},
{"example.com", []string{}, true},
{"example.com", []string{"example.com"}, 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},
}
for _, tt := range tests {
// TODO: remove this once we remove localhost insecure by default
insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8")
if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected {
t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
config := makeServiceConfig(nil, tt.insecureRegistries)
if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
}
}
}

View file

@ -13,14 +13,14 @@ import (
// 'pull': Download images from any registry (TODO)
// 'push': Upload images to any registry (TODO)
type Service struct {
insecureRegistries []string
Config *ServiceConfig
}
// NewService returns a new instance of Service ready to be
// installed no an engine.
func NewService(insecureRegistries []string) *Service {
func NewService(options *Options) *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 {
eng.Register("auth", s.Auth)
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
}
@ -39,15 +42,18 @@ func (s *Service) Auth(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig)
if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() {
endpoint, err := NewEndpoint(addr, s.insecureRegistries)
if authConfig.ServerAddress != "" {
index, err := ResolveIndexInfo(job, authConfig.ServerAddress)
if err != nil {
return job.Error(err)
}
if _, err := endpoint.Ping(); err != nil {
return job.Error(err)
if !index.Official {
endpoint, err := NewEndpoint(index)
if err != nil {
return job.Error(err)
}
authConfig.ServerAddress = endpoint.String()
}
authConfig.ServerAddress = endpoint.String()
}
status, err := Login(authConfig, HTTPRequestFactory(nil))
@ -87,12 +93,12 @@ func (s *Service) Search(job *engine.Job) engine.Status {
job.GetenvJson("authConfig", authConfig)
job.GetenvJson("metaHeaders", metaHeaders)
hostname, term, err := ResolveRepositoryName(term)
repoInfo, err := ResolveRepositoryInfo(job, term)
if err != nil {
return job.Error(err)
}
endpoint, err := NewEndpoint(hostname, s.insecureRegistries)
// *TODO: Search multiple indexes.
endpoint, err := repoInfo.GetEndpoint()
if err != nil {
return job.Error(err)
}
@ -100,7 +106,7 @@ func (s *Service) Search(job *engine.Job) engine.Status {
if err != nil {
return job.Error(err)
}
results, err := r.SearchRepositories(term)
results, err := r.SearchRepositories(repoInfo.GetSearchTerm())
if err != nil {
return job.Error(err)
}
@ -116,3 +122,92 @@ func (s *Service) Search(job *engine.Job) engine.Status {
}
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
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...)
}
func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator {
return self.decorators
}
// NewRequest() creates a new *http.Request,
// applies all decorators in the HTTPRequestFactory on the request,
// then applies decorators provided by d on the request.