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:
commit
6870bde584
31 changed files with 1607 additions and 471 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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", ®istryConfig); 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())
|
||||
|
|
|
@ -19,11 +19,13 @@ import (
|
|||
const CanDaemon = true
|
||||
|
||||
var (
|
||||
daemonCfg = &daemon.Config{}
|
||||
daemonCfg = &daemon.Config{}
|
||||
registryCfg = ®istry.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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
115
graph/pull.go
115
graph/pull.go
|
@ -85,9 +85,14 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
|||
sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
||||
authConfig = ®istry.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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 == "" {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
24
opts/opts.go
24
opts/opts.go
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
382
registry/config.go
Normal 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
49
registry/config_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue