Cleanup: Replace ResolveRepositoryName with RepositoryInfo{}
| ... | ... |
@@ -222,7 +222,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
| 222 | 222 |
//Check if the given image name can be resolved |
| 223 | 223 |
if *tag != "" {
|
| 224 | 224 |
repository, tag := parsers.ParseRepositoryTag(*tag) |
| 225 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 225 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 226 | 226 |
return err |
| 227 | 227 |
} |
| 228 | 228 |
if len(tag) > 0 {
|
| ... | ... |
@@ -1148,7 +1148,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
| 1148 | 1148 |
if repository != "" {
|
| 1149 | 1149 |
//Check if the given image name can be resolved |
| 1150 | 1150 |
repo, _ := parsers.ParseRepositoryTag(repository) |
| 1151 |
- if _, _, err := registry.ResolveRepositoryName(repo); err != nil {
|
|
| 1151 |
+ if err := registry.ValidateRepositoryName(repo); err != nil {
|
|
| 1152 | 1152 |
return err |
| 1153 | 1153 |
} |
| 1154 | 1154 |
} |
| ... | ... |
@@ -1174,23 +1174,23 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
| 1174 | 1174 |
|
| 1175 | 1175 |
remote, tag := parsers.ParseRepositoryTag(name) |
| 1176 | 1176 |
|
| 1177 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 1178 |
- hostname, _, err := registry.ResolveRepositoryName(remote) |
|
| 1177 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 1178 |
+ repoInfo, err := registry.ParseRepositoryInfo(remote) |
|
| 1179 | 1179 |
if err != nil {
|
| 1180 | 1180 |
return err |
| 1181 | 1181 |
} |
| 1182 | 1182 |
// Resolve the Auth config relevant for this server |
| 1183 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1183 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1184 | 1184 |
// If we're not using a custom registry, we know the restrictions |
| 1185 | 1185 |
// applied to repository names and can warn the user in advance. |
| 1186 | 1186 |
// Custom repositories can have different rules, and we must also |
| 1187 | 1187 |
// allow pushing by image ID. |
| 1188 |
- if len(strings.SplitN(name, "/", 2)) == 1 {
|
|
| 1189 |
- username := cli.configFile.Configs[registry.IndexServerAddress()].Username |
|
| 1188 |
+ if repoInfo.Official {
|
|
| 1189 |
+ username := authConfig.Username |
|
| 1190 | 1190 |
if username == "" {
|
| 1191 | 1191 |
username = "<user>" |
| 1192 | 1192 |
} |
| 1193 |
- return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
|
|
| 1193 |
+ return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
|
| 1194 | 1194 |
} |
| 1195 | 1195 |
|
| 1196 | 1196 |
v := url.Values{}
|
| ... | ... |
@@ -1212,10 +1212,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
| 1212 | 1212 |
if err := push(authConfig); err != nil {
|
| 1213 | 1213 |
if strings.Contains(err.Error(), "Status 401") {
|
| 1214 | 1214 |
fmt.Fprintln(cli.out, "\nPlease login prior to push:") |
| 1215 |
- if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1215 |
+ if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 1216 | 1216 |
return err |
| 1217 | 1217 |
} |
| 1218 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1218 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1219 | 1219 |
return push(authConfig) |
| 1220 | 1220 |
} |
| 1221 | 1221 |
return err |
| ... | ... |
@@ -1245,8 +1245,8 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
| 1245 | 1245 |
|
| 1246 | 1246 |
v.Set("fromImage", newRemote)
|
| 1247 | 1247 |
|
| 1248 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 1249 |
- hostname, _, err := registry.ResolveRepositoryName(taglessRemote) |
|
| 1248 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 1249 |
+ repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) |
|
| 1250 | 1250 |
if err != nil {
|
| 1251 | 1251 |
return err |
| 1252 | 1252 |
} |
| ... | ... |
@@ -1254,7 +1254,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
| 1254 | 1254 |
cli.LoadConfigFile() |
| 1255 | 1255 |
|
| 1256 | 1256 |
// Resolve the Auth config relevant for this server |
| 1257 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1257 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1258 | 1258 |
|
| 1259 | 1259 |
pull := func(authConfig registry.AuthConfig) error {
|
| 1260 | 1260 |
buf, err := json.Marshal(authConfig) |
| ... | ... |
@@ -1273,10 +1273,10 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
| 1273 | 1273 |
if err := pull(authConfig); err != nil {
|
| 1274 | 1274 |
if strings.Contains(err.Error(), "Status 401") {
|
| 1275 | 1275 |
fmt.Fprintln(cli.out, "\nPlease login prior to pull:") |
| 1276 |
- if err := cli.CmdLogin(hostname); err != nil {
|
|
| 1276 |
+ if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil {
|
|
| 1277 | 1277 |
return err |
| 1278 | 1278 |
} |
| 1279 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 1279 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 1280 | 1280 |
return pull(authConfig) |
| 1281 | 1281 |
} |
| 1282 | 1282 |
return err |
| ... | ... |
@@ -1691,7 +1691,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
| 1691 | 1691 |
|
| 1692 | 1692 |
//Check if the given image name can be resolved |
| 1693 | 1693 |
if repository != "" {
|
| 1694 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 1694 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 1695 | 1695 |
return err |
| 1696 | 1696 |
} |
| 1697 | 1697 |
} |
| ... | ... |
@@ -2002,7 +2002,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
| 2002 | 2002 |
) |
| 2003 | 2003 |
|
| 2004 | 2004 |
//Check if the given image name can be resolved |
| 2005 |
- if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
|
|
| 2005 |
+ if err := registry.ValidateRepositoryName(repository); err != nil {
|
|
| 2006 | 2006 |
return err |
| 2007 | 2007 |
} |
| 2008 | 2008 |
v.Set("repo", repository)
|
| ... | ... |
@@ -2032,8 +2032,8 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
| 2032 | 2032 |
v.Set("fromImage", repos)
|
| 2033 | 2033 |
v.Set("tag", tag)
|
| 2034 | 2034 |
|
| 2035 |
- // Resolve the Repository name from fqn to hostname + name |
|
| 2036 |
- hostname, _, err := registry.ResolveRepositoryName(repos) |
|
| 2035 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 2036 |
+ repoInfo, err := registry.ParseRepositoryInfo(repos) |
|
| 2037 | 2037 |
if err != nil {
|
| 2038 | 2038 |
return err |
| 2039 | 2039 |
} |
| ... | ... |
@@ -2042,7 +2042,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
| 2042 | 2042 |
cli.LoadConfigFile() |
| 2043 | 2043 |
|
| 2044 | 2044 |
// Resolve the Auth config relevant for this server |
| 2045 |
- authConfig := cli.configFile.ResolveAuthConfig(hostname) |
|
| 2045 |
+ authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) |
|
| 2046 | 2046 |
buf, err := json.Marshal(authConfig) |
| 2047 | 2047 |
if err != nil {
|
| 2048 | 2048 |
return err |
| ... | ... |
@@ -66,7 +66,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
|
| 66 | 66 |
if passAuthInfo {
|
| 67 | 67 |
cli.LoadConfigFile() |
| 68 | 68 |
// Resolve the Auth config relevant for this server |
| 69 |
- authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) |
|
| 69 |
+ authConfig := cli.configFile.Configs[registry.IndexServerAddress()] |
|
| 70 | 70 |
getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
|
| 71 | 71 |
buf, err := json.Marshal(authConfig) |
| 72 | 72 |
if err != nil {
|
| ... | ... |
@@ -260,7 +260,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
| 260 | 260 |
sigchan := make(chan os.Signal, 1) |
| 261 | 261 |
gosignal.Notify(sigchan, signal.SIGWINCH) |
| 262 | 262 |
go func() {
|
| 263 |
- for _ = range sigchan {
|
|
| 263 |
+ // This tmp := range..., _ = tmp workaround is needed to |
|
| 264 |
+ // suppress gofmt warnings while still preserve go1.3 compatibility |
|
| 265 |
+ for tmp := range sigchan {
|
|
| 266 |
+ _ = tmp |
|
| 264 | 267 |
cli.resizeTty(id, isExec) |
| 265 | 268 |
} |
| 266 | 269 |
}() |
| ... | ... |
@@ -427,17 +427,17 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
|
| 427 | 427 |
if tag == "" {
|
| 428 | 428 |
tag = "latest" |
| 429 | 429 |
} |
| 430 |
+ job := b.Engine.Job("pull", remote, tag)
|
|
| 430 | 431 |
pullRegistryAuth := b.AuthConfig |
| 431 | 432 |
if len(b.AuthConfigFile.Configs) > 0 {
|
| 432 | 433 |
// The request came with a full auth config file, we prefer to use that |
| 433 |
- endpoint, _, err := registry.ResolveRepositoryName(remote) |
|
| 434 |
+ repoInfo, err := registry.ResolveRepositoryInfo(job, remote) |
|
| 434 | 435 |
if err != nil {
|
| 435 | 436 |
return nil, err |
| 436 | 437 |
} |
| 437 |
- resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint) |
|
| 438 |
+ resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) |
|
| 438 | 439 |
pullRegistryAuth = &resolvedAuth |
| 439 | 440 |
} |
| 440 |
- job := b.Engine.Job("pull", remote, tag)
|
|
| 441 | 441 |
job.SetenvBool("json", b.StreamFormatter.Json())
|
| 442 | 442 |
job.SetenvBool("parallel", true)
|
| 443 | 443 |
job.SetenvJson("authConfig", pullRegistryAuth)
|
| ... | ... |
@@ -50,7 +50,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
|
| 50 | 50 |
|
| 51 | 51 |
repoName, tag = parsers.ParseRepositoryTag(repoName) |
| 52 | 52 |
if repoName != "" {
|
| 53 |
- if _, _, err := registry.ResolveRepositoryName(repoName); err != nil {
|
|
| 53 |
+ if err := registry.ValidateRepositoryName(repoName); err != nil {
|
|
| 54 | 54 |
return job.Error(err) |
| 55 | 55 |
} |
| 56 | 56 |
if len(tag) > 0 {
|
| ... | ... |
@@ -23,7 +23,6 @@ type Config struct {
|
| 23 | 23 |
AutoRestart bool |
| 24 | 24 |
Dns []string |
| 25 | 25 |
DnsSearch []string |
| 26 |
- Mirrors []string |
|
| 27 | 26 |
EnableIptables bool |
| 28 | 27 |
EnableIpForward bool |
| 29 | 28 |
EnableIpMasq bool |
| ... | ... |
@@ -31,7 +30,6 @@ type Config struct {
|
| 31 | 31 |
BridgeIface string |
| 32 | 32 |
BridgeIP string |
| 33 | 33 |
FixedCIDR string |
| 34 |
- InsecureRegistries []string |
|
| 35 | 34 |
InterContainerCommunication bool |
| 36 | 35 |
GraphDriver string |
| 37 | 36 |
GraphOptions []string |
| ... | ... |
@@ -58,7 +56,6 @@ func (config *Config) InstallFlags() {
|
| 58 | 58 |
flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
|
| 59 | 59 |
flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
|
| 60 | 60 |
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)")
|
| 61 |
- 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)")
|
|
| 62 | 61 |
flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
|
| 63 | 62 |
flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
|
| 64 | 63 |
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() {
|
| 69 | 69 |
// FIXME: why the inconsistency between "hosts" and "sockets"? |
| 70 | 70 |
opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
|
| 71 | 71 |
opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
|
| 72 |
- opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
|
| 73 | 72 |
opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)")
|
| 74 |
- |
|
| 75 |
- // Localhost is by default considered as an insecure registry |
|
| 76 |
- // This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker). |
|
| 77 |
- // |
|
| 78 |
- // TODO: should we deprecate this once it is easier for people to set up a TLS registry or change |
|
| 79 |
- // daemon flags on boot2docker? |
|
| 80 |
- // If so, do not forget to check the TODO in TestIsSecure |
|
| 81 |
- config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8") |
|
| 82 | 73 |
} |
| 83 | 74 |
|
| 84 | 75 |
func getDefaultNetworkMtu() int {
|
| ... | ... |
@@ -898,7 +898,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) |
| 898 | 898 |
} |
| 899 | 899 |
|
| 900 | 900 |
log.Debugf("Creating repository list")
|
| 901 |
- repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors, config.InsecureRegistries) |
|
| 901 |
+ repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) |
|
| 902 | 902 |
if err != nil {
|
| 903 | 903 |
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
| 904 | 904 |
} |
| ... | ... |
@@ -55,6 +55,15 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
|
| 55 | 55 |
if err := cjob.Run(); err != nil {
|
| 56 | 56 |
return job.Error(err) |
| 57 | 57 |
} |
| 58 |
+ registryJob := job.Eng.Job("registry_config")
|
|
| 59 |
+ registryEnv, _ := registryJob.Stdout.AddEnv() |
|
| 60 |
+ if err := registryJob.Run(); err != nil {
|
|
| 61 |
+ return job.Error(err) |
|
| 62 |
+ } |
|
| 63 |
+ registryConfig := registry.ServiceConfig{}
|
|
| 64 |
+ if err := registryEnv.GetJson("config", ®istryConfig); err != nil {
|
|
| 65 |
+ return job.Error(err) |
|
| 66 |
+ } |
|
| 58 | 67 |
v := &engine.Env{}
|
| 59 | 68 |
v.SetJson("ID", daemon.ID)
|
| 60 | 69 |
v.SetInt("Containers", len(daemon.List()))
|
| ... | ... |
@@ -72,6 +81,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
|
| 72 | 72 |
v.Set("KernelVersion", kernelVersion)
|
| 73 | 73 |
v.Set("OperatingSystem", operatingSystem)
|
| 74 | 74 |
v.Set("IndexServerAddress", registry.IndexServerAddress())
|
| 75 |
+ v.SetJson("RegistryConfig", registryConfig)
|
|
| 75 | 76 |
v.Set("InitSha1", dockerversion.INITSHA1)
|
| 76 | 77 |
v.Set("InitPath", initPath)
|
| 77 | 78 |
v.SetInt("NCPU", runtime.NumCPU())
|
| ... | ... |
@@ -19,11 +19,13 @@ import ( |
| 19 | 19 |
const CanDaemon = true |
| 20 | 20 |
|
| 21 | 21 |
var ( |
| 22 |
- daemonCfg = &daemon.Config{}
|
|
| 22 |
+ daemonCfg = &daemon.Config{}
|
|
| 23 |
+ registryCfg = ®istry.Options{}
|
|
| 23 | 24 |
) |
| 24 | 25 |
|
| 25 | 26 |
func init() {
|
| 26 | 27 |
daemonCfg.InstallFlags() |
| 28 |
+ registryCfg.InstallFlags() |
|
| 27 | 29 |
} |
| 28 | 30 |
|
| 29 | 31 |
func mainDaemon() {
|
| ... | ... |
@@ -42,7 +44,7 @@ func mainDaemon() {
|
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 | 44 |
// load registry service |
| 45 |
- if err := registry.NewService(daemonCfg.InsecureRegistries).Install(eng); err != nil {
|
|
| 45 |
+ if err := registry.NewService(registryCfg).Install(eng); err != nil {
|
|
| 46 | 46 |
log.Fatal(err) |
| 47 | 47 |
} |
| 48 | 48 |
|
| ... | ... |
@@ -11,6 +11,7 @@ import ( |
| 11 | 11 |
"github.com/docker/docker/engine" |
| 12 | 12 |
"github.com/docker/docker/pkg/archive" |
| 13 | 13 |
"github.com/docker/docker/pkg/parsers" |
| 14 |
+ "github.com/docker/docker/registry" |
|
| 14 | 15 |
) |
| 15 | 16 |
|
| 16 | 17 |
// CmdImageExport exports all images with the given tag. All versions |
| ... | ... |
@@ -39,6 +40,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
|
| 39 | 39 |
} |
| 40 | 40 |
} |
| 41 | 41 |
for _, name := range job.Args {
|
| 42 |
+ name = registry.NormalizeLocalName(name) |
|
| 42 | 43 |
log.Debugf("Serializing %s", name)
|
| 43 | 44 |
rootRepo := s.Repositories[name] |
| 44 | 45 |
if rootRepo != nil {
|
| ... | ... |
@@ -85,9 +85,14 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
| 85 | 85 |
sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
| 86 | 86 |
authConfig = ®istry.AuthConfig{}
|
| 87 | 87 |
metaHeaders map[string][]string |
| 88 |
- mirrors []string |
|
| 89 | 88 |
) |
| 90 | 89 |
|
| 90 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 91 |
+ repoInfo, err := registry.ResolveRepositoryInfo(job, localName) |
|
| 92 |
+ if err != nil {
|
|
| 93 |
+ return job.Error(err) |
|
| 94 |
+ } |
|
| 95 |
+ |
|
| 91 | 96 |
if len(job.Args) > 1 {
|
| 92 | 97 |
tag = job.Args[1] |
| 93 | 98 |
} |
| ... | ... |
@@ -95,25 +100,19 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
| 95 | 95 |
job.GetenvJson("authConfig", authConfig)
|
| 96 | 96 |
job.GetenvJson("metaHeaders", &metaHeaders)
|
| 97 | 97 |
|
| 98 |
- c, err := s.poolAdd("pull", localName+":"+tag)
|
|
| 98 |
+ c, err := s.poolAdd("pull", repoInfo.LocalName+":"+tag)
|
|
| 99 | 99 |
if err != nil {
|
| 100 | 100 |
if c != nil {
|
| 101 | 101 |
// Another pull of the same repository is already taking place; just wait for it to finish |
| 102 |
- job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName))
|
|
| 102 |
+ job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName))
|
|
| 103 | 103 |
<-c |
| 104 | 104 |
return engine.StatusOK |
| 105 | 105 |
} |
| 106 | 106 |
return job.Error(err) |
| 107 | 107 |
} |
| 108 |
- defer s.poolRemove("pull", localName+":"+tag)
|
|
| 109 |
- |
|
| 110 |
- // Resolve the Repository name from fqn to endpoint + name |
|
| 111 |
- hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 112 |
- if err != nil {
|
|
| 113 |
- return job.Error(err) |
|
| 114 |
- } |
|
| 108 |
+ defer s.poolRemove("pull", repoInfo.LocalName+":"+tag)
|
|
| 115 | 109 |
|
| 116 |
- endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries) |
|
| 110 |
+ endpoint, err := repoInfo.GetEndpoint() |
|
| 117 | 111 |
if err != nil {
|
| 118 | 112 |
return job.Error(err) |
| 119 | 113 |
} |
| ... | ... |
@@ -123,32 +122,18 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
| 123 | 123 |
return job.Error(err) |
| 124 | 124 |
} |
| 125 | 125 |
|
| 126 |
- var isOfficial bool |
|
| 127 |
- if endpoint.VersionString(1) == registry.IndexServerAddress() {
|
|
| 128 |
- // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" |
|
| 129 |
- localName = remoteName |
|
| 130 |
- |
|
| 131 |
- isOfficial = isOfficialName(remoteName) |
|
| 132 |
- if isOfficial && strings.IndexRune(remoteName, '/') == -1 {
|
|
| 133 |
- remoteName = "library/" + remoteName |
|
| 134 |
- } |
|
| 135 |
- |
|
| 136 |
- // Use provided mirrors, if any |
|
| 137 |
- mirrors = s.mirrors |
|
| 138 |
- } |
|
| 139 |
- |
|
| 140 |
- logName := localName |
|
| 126 |
+ logName := repoInfo.LocalName |
|
| 141 | 127 |
if tag != "" {
|
| 142 | 128 |
logName += ":" + tag |
| 143 | 129 |
} |
| 144 | 130 |
|
| 145 |
- if len(mirrors) == 0 && (isOfficial || endpoint.Version == registry.APIVersion2) {
|
|
| 131 |
+ if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Official || endpoint.Version == registry.APIVersion2) {
|
|
| 146 | 132 |
j := job.Eng.Job("trust_update_base")
|
| 147 | 133 |
if err = j.Run(); err != nil {
|
| 148 | 134 |
return job.Errorf("error updating trust base graph: %s", err)
|
| 149 | 135 |
} |
| 150 | 136 |
|
| 151 |
- if err := s.pullV2Repository(job.Eng, r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err == nil {
|
|
| 137 |
+ if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil {
|
|
| 152 | 138 |
if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil {
|
| 153 | 139 |
log.Errorf("Error logging event 'pull' for %s: %s", logName, err)
|
| 154 | 140 |
} |
| ... | ... |
@@ -158,7 +143,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
| 158 | 158 |
} |
| 159 | 159 |
} |
| 160 | 160 |
|
| 161 |
- if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel"), mirrors); err != nil {
|
|
| 161 |
+ if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil {
|
|
| 162 | 162 |
return job.Error(err) |
| 163 | 163 |
} |
| 164 | 164 |
|
| ... | ... |
@@ -169,20 +154,20 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
| 169 | 169 |
return engine.StatusOK |
| 170 | 170 |
} |
| 171 | 171 |
|
| 172 |
-func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool, mirrors []string) error {
|
|
| 173 |
- out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
|
|
| 172 |
+func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
|
|
| 173 |
+ out.Write(sf.FormatStatus("", "Pulling repository %s", repoInfo.CanonicalName))
|
|
| 174 | 174 |
|
| 175 |
- repoData, err := r.GetRepositoryData(remoteName) |
|
| 175 |
+ repoData, err := r.GetRepositoryData(repoInfo.RemoteName) |
|
| 176 | 176 |
if err != nil {
|
| 177 | 177 |
if strings.Contains(err.Error(), "HTTP code: 404") {
|
| 178 |
- return fmt.Errorf("Error: image %s:%s not found", remoteName, askedTag)
|
|
| 178 |
+ return fmt.Errorf("Error: image %s:%s not found", repoInfo.RemoteName, askedTag)
|
|
| 179 | 179 |
} |
| 180 | 180 |
// Unexpected HTTP error |
| 181 | 181 |
return err |
| 182 | 182 |
} |
| 183 | 183 |
|
| 184 | 184 |
log.Debugf("Retrieving the tag list")
|
| 185 |
- tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) |
|
| 185 |
+ tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens) |
|
| 186 | 186 |
if err != nil {
|
| 187 | 187 |
log.Errorf("%v", err)
|
| 188 | 188 |
return err |
| ... | ... |
@@ -207,7 +192,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 207 | 207 |
// Otherwise, check that the tag exists and use only that one |
| 208 | 208 |
id, exists := tagsList[askedTag] |
| 209 | 209 |
if !exists {
|
| 210 |
- return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
|
|
| 210 |
+ return fmt.Errorf("Tag %s not found in repository %s", askedTag, repoInfo.CanonicalName)
|
|
| 211 | 211 |
} |
| 212 | 212 |
imageId = id |
| 213 | 213 |
repoData.ImgList[id].Tag = askedTag |
| ... | ... |
@@ -250,31 +235,29 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 250 | 250 |
} |
| 251 | 251 |
defer s.poolRemove("pull", "img:"+img.ID)
|
| 252 | 252 |
|
| 253 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
|
|
| 253 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil))
|
|
| 254 | 254 |
success := false |
| 255 | 255 |
var lastErr, err error |
| 256 | 256 |
var is_downloaded bool |
| 257 |
- if mirrors != nil {
|
|
| 258 |
- for _, ep := range mirrors {
|
|
| 259 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, localName, ep), nil))
|
|
| 260 |
- if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 261 |
- // Don't report errors when pulling from mirrors. |
|
| 262 |
- log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, localName, ep, err)
|
|
| 263 |
- continue |
|
| 264 |
- } |
|
| 265 |
- layers_downloaded = layers_downloaded || is_downloaded |
|
| 266 |
- success = true |
|
| 267 |
- break |
|
| 257 |
+ for _, ep := range repoInfo.Index.Mirrors {
|
|
| 258 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
|
|
| 259 |
+ if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 260 |
+ // Don't report errors when pulling from mirrors. |
|
| 261 |
+ log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err)
|
|
| 262 |
+ continue |
|
| 268 | 263 |
} |
| 264 |
+ layers_downloaded = layers_downloaded || is_downloaded |
|
| 265 |
+ success = true |
|
| 266 |
+ break |
|
| 269 | 267 |
} |
| 270 | 268 |
if !success {
|
| 271 | 269 |
for _, ep := range repoData.Endpoints {
|
| 272 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
|
|
| 270 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil))
|
|
| 273 | 271 |
if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
| 274 | 272 |
// It's not ideal that only the last error is returned, it would be better to concatenate the errors. |
| 275 | 273 |
// As the error is also given to the output stream the user will see the error. |
| 276 | 274 |
lastErr = err |
| 277 |
- 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))
|
|
| 275 |
+ 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))
|
|
| 278 | 276 |
continue |
| 279 | 277 |
} |
| 280 | 278 |
layers_downloaded = layers_downloaded || is_downloaded |
| ... | ... |
@@ -283,7 +266,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 283 | 283 |
} |
| 284 | 284 |
} |
| 285 | 285 |
if !success {
|
| 286 |
- err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr)
|
|
| 286 |
+ err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, repoInfo.CanonicalName, lastErr)
|
|
| 287 | 287 |
out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil)) |
| 288 | 288 |
if parallel {
|
| 289 | 289 |
errors <- err |
| ... | ... |
@@ -319,14 +302,14 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, localName, |
| 319 | 319 |
if askedTag != "" && id != imageId {
|
| 320 | 320 |
continue |
| 321 | 321 |
} |
| 322 |
- if err := s.Set(localName, tag, id, true); err != nil {
|
|
| 322 |
+ if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil {
|
|
| 323 | 323 |
return err |
| 324 | 324 |
} |
| 325 | 325 |
} |
| 326 | 326 |
|
| 327 |
- requestedTag := localName |
|
| 327 |
+ requestedTag := repoInfo.CanonicalName |
|
| 328 | 328 |
if len(askedTag) > 0 {
|
| 329 |
- requestedTag = localName + ":" + askedTag |
|
| 329 |
+ requestedTag = repoInfo.CanonicalName + ":" + askedTag |
|
| 330 | 330 |
} |
| 331 | 331 |
WriteStatus(requestedTag, out, sf, layers_downloaded) |
| 332 | 332 |
return nil |
| ... | ... |
@@ -440,40 +423,40 @@ type downloadInfo struct {
|
| 440 | 440 |
err chan error |
| 441 | 441 |
} |
| 442 | 442 |
|
| 443 |
-func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, localName, remoteName, tag string, sf *utils.StreamFormatter, parallel bool) error {
|
|
| 443 |
+func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool) error {
|
|
| 444 | 444 |
var layersDownloaded bool |
| 445 | 445 |
if tag == "" {
|
| 446 |
- log.Debugf("Pulling tag list from V2 registry for %s", remoteName)
|
|
| 447 |
- tags, err := r.GetV2RemoteTags(remoteName, nil) |
|
| 446 |
+ log.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName)
|
|
| 447 |
+ tags, err := r.GetV2RemoteTags(repoInfo.RemoteName, nil) |
|
| 448 | 448 |
if err != nil {
|
| 449 | 449 |
return err |
| 450 | 450 |
} |
| 451 | 451 |
for _, t := range tags {
|
| 452 |
- if downloaded, err := s.pullV2Tag(eng, r, out, localName, remoteName, t, sf, parallel); err != nil {
|
|
| 452 |
+ if downloaded, err := s.pullV2Tag(eng, r, out, repoInfo, t, sf, parallel); err != nil {
|
|
| 453 | 453 |
return err |
| 454 | 454 |
} else if downloaded {
|
| 455 | 455 |
layersDownloaded = true |
| 456 | 456 |
} |
| 457 | 457 |
} |
| 458 | 458 |
} else {
|
| 459 |
- if downloaded, err := s.pullV2Tag(eng, r, out, localName, remoteName, tag, sf, parallel); err != nil {
|
|
| 459 |
+ if downloaded, err := s.pullV2Tag(eng, r, out, repoInfo, tag, sf, parallel); err != nil {
|
|
| 460 | 460 |
return err |
| 461 | 461 |
} else if downloaded {
|
| 462 | 462 |
layersDownloaded = true |
| 463 | 463 |
} |
| 464 | 464 |
} |
| 465 | 465 |
|
| 466 |
- requestedTag := localName |
|
| 466 |
+ requestedTag := repoInfo.CanonicalName |
|
| 467 | 467 |
if len(tag) > 0 {
|
| 468 |
- requestedTag = localName + ":" + tag |
|
| 468 |
+ requestedTag = repoInfo.CanonicalName + ":" + tag |
|
| 469 | 469 |
} |
| 470 | 470 |
WriteStatus(requestedTag, out, sf, layersDownloaded) |
| 471 | 471 |
return nil |
| 472 | 472 |
} |
| 473 | 473 |
|
| 474 |
-func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, localName, remoteName, tag string, sf *utils.StreamFormatter, parallel bool) (bool, error) {
|
|
| 474 |
+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) {
|
|
| 475 | 475 |
log.Debugf("Pulling tag from V2 registry: %q", tag)
|
| 476 |
- manifestBytes, err := r.GetV2ImageManifest(remoteName, tag, nil) |
|
| 476 |
+ manifestBytes, err := r.GetV2ImageManifest(repoInfo.RemoteName, tag, nil) |
|
| 477 | 477 |
if err != nil {
|
| 478 | 478 |
return false, err |
| 479 | 479 |
} |
| ... | ... |
@@ -488,9 +471,9 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri |
| 488 | 488 |
} |
| 489 | 489 |
|
| 490 | 490 |
if verified {
|
| 491 |
- out.Write(sf.FormatStatus(localName+":"+tag, "The image you are pulling has been verified")) |
|
| 491 |
+ out.Write(sf.FormatStatus(repoInfo.CanonicalName+":"+tag, "The image you are pulling has been verified")) |
|
| 492 | 492 |
} else {
|
| 493 |
- out.Write(sf.FormatStatus(tag, "Pulling from %s", localName)) |
|
| 493 |
+ out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName)) |
|
| 494 | 494 |
} |
| 495 | 495 |
|
| 496 | 496 |
if len(manifest.FSLayers) == 0 {
|
| ... | ... |
@@ -542,7 +525,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri |
| 542 | 542 |
return err |
| 543 | 543 |
} |
| 544 | 544 |
|
| 545 |
- r, l, err := r.GetV2ImageBlobReader(remoteName, sumType, checksum, nil) |
|
| 545 |
+ r, l, err := r.GetV2ImageBlobReader(repoInfo.RemoteName, sumType, checksum, nil) |
|
| 546 | 546 |
if err != nil {
|
| 547 | 547 |
return err |
| 548 | 548 |
} |
| ... | ... |
@@ -605,7 +588,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri |
| 605 | 605 |
|
| 606 | 606 |
} |
| 607 | 607 |
|
| 608 |
- if err = s.Set(localName, tag, downloads[0].img.ID, true); err != nil {
|
|
| 608 |
+ if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil {
|
|
| 609 | 609 |
return false, err |
| 610 | 610 |
} |
| 611 | 611 |
|
| ... | ... |
@@ -61,7 +61,7 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string |
| 61 | 61 |
return imageList, tagsByImage, nil |
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 |
-func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
|
|
| 64 |
+func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
|
|
| 65 | 65 |
out = utils.NewWriteFlusher(out) |
| 66 | 66 |
log.Debugf("Local repo: %s", localRepo)
|
| 67 | 67 |
imgList, tagsByImage, err := s.getImageList(localRepo, tag) |
| ... | ... |
@@ -104,7 +104,7 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, |
| 104 | 104 |
|
| 105 | 105 |
// Register all the images in a repository with the registry |
| 106 | 106 |
// If an image is not in this list it will not be associated with the repository |
| 107 |
- repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) |
|
| 107 |
+ repoData, err = r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, false, nil) |
|
| 108 | 108 |
if err != nil {
|
| 109 | 109 |
return err |
| 110 | 110 |
} |
| ... | ... |
@@ -114,11 +114,11 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, |
| 114 | 114 |
nTag = len(localRepo) |
| 115 | 115 |
} |
| 116 | 116 |
for _, ep := range repoData.Endpoints {
|
| 117 |
- out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag))
|
|
| 117 |
+ out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", repoInfo.CanonicalName, nTag))
|
|
| 118 | 118 |
for _, imgId := range imgList {
|
| 119 | 119 |
if err := r.LookupRemoteImage(imgId, ep, repoData.Tokens); err != nil {
|
| 120 | 120 |
log.Errorf("Error in LookupRemoteImage: %s", err)
|
| 121 |
- if _, err := s.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
|
|
| 121 |
+ if _, err := s.pushImage(r, out, imgId, ep, repoData.Tokens, sf); err != nil {
|
|
| 122 | 122 |
// FIXME: Continue on error? |
| 123 | 123 |
return err |
| 124 | 124 |
} |
| ... | ... |
@@ -126,23 +126,23 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, localName, |
| 126 | 126 |
out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
|
| 127 | 127 |
} |
| 128 | 128 |
for _, tag := range tagsByImage[imgId] {
|
| 129 |
- out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag))
|
|
| 129 |
+ out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+repoInfo.RemoteName+"/tags/"+tag))
|
|
| 130 | 130 |
|
| 131 |
- if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil {
|
|
| 131 |
+ if err := r.PushRegistryTag(repoInfo.RemoteName, imgId, tag, ep, repoData.Tokens); err != nil {
|
|
| 132 | 132 |
return err |
| 133 | 133 |
} |
| 134 | 134 |
} |
| 135 | 135 |
} |
| 136 | 136 |
} |
| 137 | 137 |
|
| 138 |
- if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil {
|
|
| 138 |
+ if _, err := r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, true, repoData.Endpoints); err != nil {
|
|
| 139 | 139 |
return err |
| 140 | 140 |
} |
| 141 | 141 |
|
| 142 | 142 |
return nil |
| 143 | 143 |
} |
| 144 | 144 |
|
| 145 |
-func (s *TagStore) pushImage(r *registry.Session, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
|
| 145 |
+func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
|
| 146 | 146 |
out = utils.NewWriteFlusher(out) |
| 147 | 147 |
jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) |
| 148 | 148 |
if err != nil {
|
| ... | ... |
@@ -199,26 +199,27 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
|
| 199 | 199 |
metaHeaders map[string][]string |
| 200 | 200 |
) |
| 201 | 201 |
|
| 202 |
+ // Resolve the Repository name from fqn to RepositoryInfo |
|
| 203 |
+ repoInfo, err := registry.ResolveRepositoryInfo(job, localName) |
|
| 204 |
+ if err != nil {
|
|
| 205 |
+ return job.Error(err) |
|
| 206 |
+ } |
|
| 207 |
+ |
|
| 202 | 208 |
tag := job.Getenv("tag")
|
| 203 | 209 |
job.GetenvJson("authConfig", authConfig)
|
| 204 | 210 |
job.GetenvJson("metaHeaders", &metaHeaders)
|
| 205 |
- if _, err := s.poolAdd("push", localName); err != nil {
|
|
| 206 |
- return job.Error(err) |
|
| 207 |
- } |
|
| 208 |
- defer s.poolRemove("push", localName)
|
|
| 209 | 211 |
|
| 210 |
- // Resolve the Repository name from fqn to endpoint + name |
|
| 211 |
- hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 212 |
- if err != nil {
|
|
| 212 |
+ if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil {
|
|
| 213 | 213 |
return job.Error(err) |
| 214 | 214 |
} |
| 215 |
+ defer s.poolRemove("push", repoInfo.LocalName)
|
|
| 215 | 216 |
|
| 216 |
- endpoint, err := registry.NewEndpoint(hostname, s.insecureRegistries) |
|
| 217 |
+ endpoint, err := repoInfo.GetEndpoint() |
|
| 217 | 218 |
if err != nil {
|
| 218 | 219 |
return job.Error(err) |
| 219 | 220 |
} |
| 220 | 221 |
|
| 221 |
- img, err := s.graph.Get(localName) |
|
| 222 |
+ img, err := s.graph.Get(repoInfo.LocalName) |
|
| 222 | 223 |
r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) |
| 223 | 224 |
if err2 != nil {
|
| 224 | 225 |
return job.Error(err2) |
| ... | ... |
@@ -227,12 +228,12 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
|
| 227 | 227 |
if err != nil {
|
| 228 | 228 |
reposLen := 1 |
| 229 | 229 |
if tag == "" {
|
| 230 |
- reposLen = len(s.Repositories[localName]) |
|
| 230 |
+ reposLen = len(s.Repositories[repoInfo.LocalName]) |
|
| 231 | 231 |
} |
| 232 |
- job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))
|
|
| 232 |
+ job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
|
|
| 233 | 233 |
// If it fails, try to get the repository |
| 234 |
- if localRepo, exists := s.Repositories[localName]; exists {
|
|
| 235 |
- if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
|
|
| 234 |
+ if localRepo, exists := s.Repositories[repoInfo.LocalName]; exists {
|
|
| 235 |
+ if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil {
|
|
| 236 | 236 |
return job.Error(err) |
| 237 | 237 |
} |
| 238 | 238 |
return engine.StatusOK |
| ... | ... |
@@ -241,8 +242,8 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
|
| 241 | 241 |
} |
| 242 | 242 |
|
| 243 | 243 |
var token []string |
| 244 |
- job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
|
|
| 245 |
- if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint.String(), token, sf); err != nil {
|
|
| 244 |
+ job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", repoInfo.CanonicalName))
|
|
| 245 |
+ if _, err := s.pushImage(r, job.Stdout, img.ID, endpoint.String(), token, sf); err != nil {
|
|
| 246 | 246 |
return job.Error(err) |
| 247 | 247 |
} |
| 248 | 248 |
return engine.StatusOK |
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
|
| 14 | 14 |
"github.com/docker/docker/image" |
| 15 | 15 |
"github.com/docker/docker/pkg/parsers" |
| 16 |
+ "github.com/docker/docker/registry" |
|
| 16 | 17 |
"github.com/docker/docker/utils" |
| 17 | 18 |
) |
| 18 | 19 |
|
| ... | ... |
@@ -23,11 +24,9 @@ var ( |
| 23 | 23 |
) |
| 24 | 24 |
|
| 25 | 25 |
type TagStore struct {
|
| 26 |
- path string |
|
| 27 |
- graph *Graph |
|
| 28 |
- mirrors []string |
|
| 29 |
- insecureRegistries []string |
|
| 30 |
- Repositories map[string]Repository |
|
| 26 |
+ path string |
|
| 27 |
+ graph *Graph |
|
| 28 |
+ Repositories map[string]Repository |
|
| 31 | 29 |
sync.Mutex |
| 32 | 30 |
// FIXME: move push/pull-related fields |
| 33 | 31 |
// to a helper type |
| ... | ... |
@@ -55,20 +54,18 @@ func (r Repository) Contains(u Repository) bool {
|
| 55 | 55 |
return true |
| 56 | 56 |
} |
| 57 | 57 |
|
| 58 |
-func NewTagStore(path string, graph *Graph, mirrors []string, insecureRegistries []string) (*TagStore, error) {
|
|
| 58 |
+func NewTagStore(path string, graph *Graph) (*TagStore, error) {
|
|
| 59 | 59 |
abspath, err := filepath.Abs(path) |
| 60 | 60 |
if err != nil {
|
| 61 | 61 |
return nil, err |
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 | 64 |
store := &TagStore{
|
| 65 |
- path: abspath, |
|
| 66 |
- graph: graph, |
|
| 67 |
- mirrors: mirrors, |
|
| 68 |
- insecureRegistries: insecureRegistries, |
|
| 69 |
- Repositories: make(map[string]Repository), |
|
| 70 |
- pullingPool: make(map[string]chan struct{}),
|
|
| 71 |
- pushingPool: make(map[string]chan struct{}),
|
|
| 65 |
+ path: abspath, |
|
| 66 |
+ graph: graph, |
|
| 67 |
+ Repositories: make(map[string]Repository), |
|
| 68 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 69 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 72 | 70 |
} |
| 73 | 71 |
// Load the json file if it exists, otherwise create it. |
| 74 | 72 |
if err := store.reload(); os.IsNotExist(err) {
|
| ... | ... |
@@ -178,6 +175,7 @@ func (store *TagStore) Delete(repoName, tag string) (bool, error) {
|
| 178 | 178 |
if err := store.reload(); err != nil {
|
| 179 | 179 |
return false, err |
| 180 | 180 |
} |
| 181 |
+ repoName = registry.NormalizeLocalName(repoName) |
|
| 181 | 182 |
if r, exists := store.Repositories[repoName]; exists {
|
| 182 | 183 |
if tag != "" {
|
| 183 | 184 |
if _, exists2 := r[tag]; exists2 {
|
| ... | ... |
@@ -219,6 +217,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
|
| 219 | 219 |
return err |
| 220 | 220 |
} |
| 221 | 221 |
var repo Repository |
| 222 |
+ repoName = registry.NormalizeLocalName(repoName) |
|
| 222 | 223 |
if r, exists := store.Repositories[repoName]; exists {
|
| 223 | 224 |
repo = r |
| 224 | 225 |
if old, exists := store.Repositories[repoName][tag]; exists && !force {
|
| ... | ... |
@@ -238,6 +237,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
|
| 238 | 238 |
if err := store.reload(); err != nil {
|
| 239 | 239 |
return nil, err |
| 240 | 240 |
} |
| 241 |
+ repoName = registry.NormalizeLocalName(repoName) |
|
| 241 | 242 |
if r, exists := store.Repositories[repoName]; exists {
|
| 242 | 243 |
return r, nil |
| 243 | 244 |
} |
| ... | ... |
@@ -279,20 +279,6 @@ func (store *TagStore) GetRepoRefs() map[string][]string {
|
| 279 | 279 |
return reporefs |
| 280 | 280 |
} |
| 281 | 281 |
|
| 282 |
-// isOfficialName returns whether a repo name is considered an official |
|
| 283 |
-// repository. Official repositories are repos with names within |
|
| 284 |
-// the library namespace or which default to the library namespace |
|
| 285 |
-// by not providing one. |
|
| 286 |
-func isOfficialName(name string) bool {
|
|
| 287 |
- if strings.HasPrefix(name, "library/") {
|
|
| 288 |
- return true |
|
| 289 |
- } |
|
| 290 |
- if strings.IndexRune(name, '/') == -1 {
|
|
| 291 |
- return true |
|
| 292 |
- } |
|
| 293 |
- return false |
|
| 294 |
-} |
|
| 295 |
- |
|
| 296 | 282 |
// Validate the name of a repository |
| 297 | 283 |
func validateRepoName(name string) error {
|
| 298 | 284 |
if name == "" {
|
| ... | ... |
@@ -15,8 +15,12 @@ import ( |
| 15 | 15 |
) |
| 16 | 16 |
|
| 17 | 17 |
const ( |
| 18 |
- testImageName = "myapp" |
|
| 19 |
- testImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234" |
|
| 18 |
+ testOfficialImageName = "myapp" |
|
| 19 |
+ testOfficialImageID = "1a2d3c4d4e5fa2d2a21acea242a5e2345d3aefc3e7dfa2a2a2a21a2a2ad2d234" |
|
| 20 |
+ testOfficialImageIDShort = "1a2d3c4d4e5f" |
|
| 21 |
+ testPrivateImageName = "127.0.0.1:8000/privateapp" |
|
| 22 |
+ testPrivateImageID = "5bc255f8699e4ee89ac4469266c3d11515da88fdcbde45d7b069b636ff4efd81" |
|
| 23 |
+ testPrivateImageIDShort = "5bc255f8699e" |
|
| 20 | 24 |
) |
| 21 | 25 |
|
| 22 | 26 |
func fakeTar() (io.Reader, error) {
|
| ... | ... |
@@ -53,19 +57,30 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
|
| 53 | 53 |
if err != nil {
|
| 54 | 54 |
t.Fatal(err) |
| 55 | 55 |
} |
| 56 |
- store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil) |
|
| 56 |
+ store, err := NewTagStore(path.Join(root, "tags"), graph) |
|
| 57 | 57 |
if err != nil {
|
| 58 | 58 |
t.Fatal(err) |
| 59 | 59 |
} |
| 60 |
- archive, err := fakeTar() |
|
| 60 |
+ officialArchive, err := fakeTar() |
|
| 61 | 61 |
if err != nil {
|
| 62 | 62 |
t.Fatal(err) |
| 63 | 63 |
} |
| 64 |
- img := &image.Image{ID: testImageID}
|
|
| 65 |
- if err := graph.Register(img, archive); err != nil {
|
|
| 64 |
+ img := &image.Image{ID: testOfficialImageID}
|
|
| 65 |
+ if err := graph.Register(img, officialArchive); err != nil {
|
|
| 66 | 66 |
t.Fatal(err) |
| 67 | 67 |
} |
| 68 |
- if err := store.Set(testImageName, "", testImageID, false); err != nil {
|
|
| 68 |
+ if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil {
|
|
| 69 |
+ t.Fatal(err) |
|
| 70 |
+ } |
|
| 71 |
+ privateArchive, err := fakeTar() |
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ t.Fatal(err) |
|
| 74 |
+ } |
|
| 75 |
+ img = &image.Image{ID: testPrivateImageID}
|
|
| 76 |
+ if err := graph.Register(img, privateArchive); err != nil {
|
|
| 77 |
+ t.Fatal(err) |
|
| 78 |
+ } |
|
| 79 |
+ if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil {
|
|
| 69 | 80 |
t.Fatal(err) |
| 70 | 81 |
} |
| 71 | 82 |
return store |
| ... | ... |
@@ -80,39 +95,65 @@ func TestLookupImage(t *testing.T) {
|
| 80 | 80 |
store := mkTestTagStore(tmp, t) |
| 81 | 81 |
defer store.graph.driver.Cleanup() |
| 82 | 82 |
|
| 83 |
- if img, err := store.LookupImage(testImageName); err != nil {
|
|
| 84 |
- t.Fatal(err) |
|
| 85 |
- } else if img == nil {
|
|
| 86 |
- t.Errorf("Expected 1 image, none found")
|
|
| 87 |
- } |
|
| 88 |
- if img, err := store.LookupImage(testImageName + ":" + DEFAULTTAG); err != nil {
|
|
| 89 |
- t.Fatal(err) |
|
| 90 |
- } else if img == nil {
|
|
| 91 |
- t.Errorf("Expected 1 image, none found")
|
|
| 92 |
- } |
|
| 93 |
- |
|
| 94 |
- if img, err := store.LookupImage(testImageName + ":" + "fail"); err == nil {
|
|
| 95 |
- t.Errorf("Expected error, none found")
|
|
| 96 |
- } else if img != nil {
|
|
| 97 |
- t.Errorf("Expected 0 image, 1 found")
|
|
| 98 |
- } |
|
| 99 |
- |
|
| 100 |
- if img, err := store.LookupImage("fail:fail"); err == nil {
|
|
| 101 |
- t.Errorf("Expected error, none found")
|
|
| 102 |
- } else if img != nil {
|
|
| 103 |
- t.Errorf("Expected 0 image, 1 found")
|
|
| 83 |
+ officialLookups := []string{
|
|
| 84 |
+ testOfficialImageID, |
|
| 85 |
+ testOfficialImageIDShort, |
|
| 86 |
+ testOfficialImageName + ":" + testOfficialImageID, |
|
| 87 |
+ testOfficialImageName + ":" + testOfficialImageIDShort, |
|
| 88 |
+ testOfficialImageName, |
|
| 89 |
+ testOfficialImageName + ":" + DEFAULTTAG, |
|
| 90 |
+ "docker.io/" + testOfficialImageName, |
|
| 91 |
+ "docker.io/" + testOfficialImageName + ":" + DEFAULTTAG, |
|
| 92 |
+ "index.docker.io/" + testOfficialImageName, |
|
| 93 |
+ "index.docker.io/" + testOfficialImageName + ":" + DEFAULTTAG, |
|
| 94 |
+ "library/" + testOfficialImageName, |
|
| 95 |
+ "library/" + testOfficialImageName + ":" + DEFAULTTAG, |
|
| 96 |
+ "docker.io/library/" + testOfficialImageName, |
|
| 97 |
+ "docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG, |
|
| 98 |
+ "index.docker.io/library/" + testOfficialImageName, |
|
| 99 |
+ "index.docker.io/library/" + testOfficialImageName + ":" + DEFAULTTAG, |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ privateLookups := []string{
|
|
| 103 |
+ testPrivateImageID, |
|
| 104 |
+ testPrivateImageIDShort, |
|
| 105 |
+ testPrivateImageName + ":" + testPrivateImageID, |
|
| 106 |
+ testPrivateImageName + ":" + testPrivateImageIDShort, |
|
| 107 |
+ testPrivateImageName, |
|
| 108 |
+ testPrivateImageName + ":" + DEFAULTTAG, |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ invalidLookups := []string{
|
|
| 112 |
+ testOfficialImageName + ":" + "fail", |
|
| 113 |
+ "fail:fail", |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ for _, name := range officialLookups {
|
|
| 117 |
+ if img, err := store.LookupImage(name); err != nil {
|
|
| 118 |
+ t.Errorf("Error looking up %s: %s", name, err)
|
|
| 119 |
+ } else if img == nil {
|
|
| 120 |
+ t.Errorf("Expected 1 image, none found: %s", name)
|
|
| 121 |
+ } else if img.ID != testOfficialImageID {
|
|
| 122 |
+ t.Errorf("Expected ID '%s' found '%s'", testOfficialImageID, img.ID)
|
|
| 123 |
+ } |
|
| 104 | 124 |
} |
| 105 | 125 |
|
| 106 |
- if img, err := store.LookupImage(testImageID); err != nil {
|
|
| 107 |
- t.Fatal(err) |
|
| 108 |
- } else if img == nil {
|
|
| 109 |
- t.Errorf("Expected 1 image, none found")
|
|
| 126 |
+ for _, name := range privateLookups {
|
|
| 127 |
+ if img, err := store.LookupImage(name); err != nil {
|
|
| 128 |
+ t.Errorf("Error looking up %s: %s", name, err)
|
|
| 129 |
+ } else if img == nil {
|
|
| 130 |
+ t.Errorf("Expected 1 image, none found: %s", name)
|
|
| 131 |
+ } else if img.ID != testPrivateImageID {
|
|
| 132 |
+ t.Errorf("Expected ID '%s' found '%s'", testPrivateImageID, img.ID)
|
|
| 133 |
+ } |
|
| 110 | 134 |
} |
| 111 | 135 |
|
| 112 |
- if img, err := store.LookupImage(testImageName + ":" + testImageID); err != nil {
|
|
| 113 |
- t.Fatal(err) |
|
| 114 |
- } else if img == nil {
|
|
| 115 |
- t.Errorf("Expected 1 image, none found")
|
|
| 136 |
+ for _, name := range invalidLookups {
|
|
| 137 |
+ if img, err := store.LookupImage(name); err == nil {
|
|
| 138 |
+ t.Errorf("Expected error, none found: %s", name)
|
|
| 139 |
+ } else if img != nil {
|
|
| 140 |
+ t.Errorf("Expected 0 image, 1 found: %s", name)
|
|
| 141 |
+ } |
|
| 116 | 142 |
} |
| 117 | 143 |
} |
| 118 | 144 |
|
| ... | ... |
@@ -133,18 +174,3 @@ func TestInvalidTagName(t *testing.T) {
|
| 133 | 133 |
} |
| 134 | 134 |
} |
| 135 | 135 |
} |
| 136 |
- |
|
| 137 |
-func TestOfficialName(t *testing.T) {
|
|
| 138 |
- names := map[string]bool{
|
|
| 139 |
- "library/ubuntu": true, |
|
| 140 |
- "nonlibrary/ubuntu": false, |
|
| 141 |
- "ubuntu": true, |
|
| 142 |
- "other/library": false, |
|
| 143 |
- } |
|
| 144 |
- for name, isOfficial := range names {
|
|
| 145 |
- result := isOfficialName(name) |
|
| 146 |
- if result != isOfficial {
|
|
| 147 |
- t.Errorf("Unexpected result for %s\n\tExpecting: %v\n\tActual: %v", name, isOfficial, result)
|
|
| 148 |
- } |
|
| 149 |
- } |
|
| 150 |
-} |
| ... | ... |
@@ -4301,3 +4301,24 @@ func TestBuildRenamedDockerfile(t *testing.T) {
|
| 4301 | 4301 |
|
| 4302 | 4302 |
logDone("build - rename dockerfile")
|
| 4303 | 4303 |
} |
| 4304 |
+ |
|
| 4305 |
+func TestBuildFromOfficialNames(t *testing.T) {
|
|
| 4306 |
+ name := "testbuildfromofficial" |
|
| 4307 |
+ fromNames := []string{
|
|
| 4308 |
+ "busybox", |
|
| 4309 |
+ "docker.io/busybox", |
|
| 4310 |
+ "index.docker.io/busybox", |
|
| 4311 |
+ "library/busybox", |
|
| 4312 |
+ "docker.io/library/busybox", |
|
| 4313 |
+ "index.docker.io/library/busybox", |
|
| 4314 |
+ } |
|
| 4315 |
+ for idx, fromName := range fromNames {
|
|
| 4316 |
+ imgName := fmt.Sprintf("%s%d", name, idx)
|
|
| 4317 |
+ _, err := buildImage(imgName, "FROM "+fromName, true) |
|
| 4318 |
+ if err != nil {
|
|
| 4319 |
+ t.Errorf("Build failed using FROM %s: %s", fromName, err)
|
|
| 4320 |
+ } |
|
| 4321 |
+ deleteImages(imgName) |
|
| 4322 |
+ } |
|
| 4323 |
+ logDone("build - from official names")
|
|
| 4324 |
+} |
| ... | ... |
@@ -2,6 +2,7 @@ package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"os/exec" |
| 5 |
+ "strings" |
|
| 5 | 6 |
"testing" |
| 6 | 7 |
) |
| 7 | 8 |
|
| ... | ... |
@@ -24,3 +25,33 @@ func TestPullNonExistingImage(t *testing.T) {
|
| 24 | 24 |
} |
| 25 | 25 |
logDone("pull - pull fooblahblah1234 (non-existing image)")
|
| 26 | 26 |
} |
| 27 |
+ |
|
| 28 |
+// pulling an image from the central registry using official names should work |
|
| 29 |
+// ensure all pulls result in the same image |
|
| 30 |
+func TestPullImageOfficialNames(t *testing.T) {
|
|
| 31 |
+ names := []string{
|
|
| 32 |
+ "docker.io/hello-world", |
|
| 33 |
+ "index.docker.io/hello-world", |
|
| 34 |
+ "library/hello-world", |
|
| 35 |
+ "docker.io/library/hello-world", |
|
| 36 |
+ "index.docker.io/library/hello-world", |
|
| 37 |
+ } |
|
| 38 |
+ for _, name := range names {
|
|
| 39 |
+ pullCmd := exec.Command(dockerBinary, "pull", name) |
|
| 40 |
+ out, exitCode, err := runCommandWithOutput(pullCmd) |
|
| 41 |
+ if err != nil || exitCode != 0 {
|
|
| 42 |
+ t.Errorf("pulling the '%s' image from the registry has failed: %s", name, err)
|
|
| 43 |
+ continue |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ // ensure we don't have multiple image names. |
|
| 47 |
+ imagesCmd := exec.Command(dockerBinary, "images") |
|
| 48 |
+ out, _, err = runCommandWithOutput(imagesCmd) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ t.Errorf("listing images failed with errors: %v", err)
|
|
| 51 |
+ } else if strings.Contains(out, name) {
|
|
| 52 |
+ t.Errorf("images should not have listed '%s'", name)
|
|
| 53 |
+ } |
|
| 54 |
+ } |
|
| 55 |
+ logDone("pull - pull official names")
|
|
| 56 |
+} |
| ... | ... |
@@ -132,3 +132,49 @@ func TestTagExistedNameWithForce(t *testing.T) {
|
| 132 | 132 |
|
| 133 | 133 |
logDone("tag - busybox with an existed tag name with -f option work")
|
| 134 | 134 |
} |
| 135 |
+ |
|
| 136 |
+// ensure tagging using official names works |
|
| 137 |
+// ensure all tags result in the same name |
|
| 138 |
+func TestTagOfficialNames(t *testing.T) {
|
|
| 139 |
+ names := []string{
|
|
| 140 |
+ "docker.io/busybox", |
|
| 141 |
+ "index.docker.io/busybox", |
|
| 142 |
+ "library/busybox", |
|
| 143 |
+ "docker.io/library/busybox", |
|
| 144 |
+ "index.docker.io/library/busybox", |
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ for _, name := range names {
|
|
| 148 |
+ tagCmd := exec.Command(dockerBinary, "tag", "-f", "busybox:latest", name+":latest") |
|
| 149 |
+ out, exitCode, err := runCommandWithOutput(tagCmd) |
|
| 150 |
+ if err != nil || exitCode != 0 {
|
|
| 151 |
+ t.Errorf("tag busybox %v should have worked: %s, %s", name, err, out)
|
|
| 152 |
+ continue |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ // ensure we don't have multiple tag names. |
|
| 156 |
+ imagesCmd := exec.Command(dockerBinary, "images") |
|
| 157 |
+ out, _, err = runCommandWithOutput(imagesCmd) |
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ t.Errorf("listing images failed with errors: %v, %s", err, out)
|
|
| 160 |
+ } else if strings.Contains(out, name) {
|
|
| 161 |
+ t.Errorf("images should not have listed '%s'", name)
|
|
| 162 |
+ deleteImages(name + ":latest") |
|
| 163 |
+ } else {
|
|
| 164 |
+ logMessage := fmt.Sprintf("tag official name - busybox %v", name)
|
|
| 165 |
+ logDone(logMessage) |
|
| 166 |
+ } |
|
| 167 |
+ } |
|
| 168 |
+ |
|
| 169 |
+ for _, name := range names {
|
|
| 170 |
+ tagCmd := exec.Command(dockerBinary, "tag", "-f", name+":latest", "fooo/bar:latest") |
|
| 171 |
+ _, exitCode, err := runCommandWithOutput(tagCmd) |
|
| 172 |
+ if err != nil || exitCode != 0 {
|
|
| 173 |
+ t.Errorf("tag %v fooo/bar should have worked: %s", name, err)
|
|
| 174 |
+ continue |
|
| 175 |
+ } |
|
| 176 |
+ deleteImages("fooo/bar:latest")
|
|
| 177 |
+ logMessage := fmt.Sprintf("tag official name - %v fooo/bar", name)
|
|
| 178 |
+ logDone(logMessage) |
|
| 179 |
+ } |
|
| 180 |
+} |
| ... | ... |
@@ -20,6 +20,7 @@ import ( |
| 20 | 20 |
"github.com/docker/docker/daemon" |
| 21 | 21 |
"github.com/docker/docker/engine" |
| 22 | 22 |
flag "github.com/docker/docker/pkg/mflag" |
| 23 |
+ "github.com/docker/docker/registry" |
|
| 23 | 24 |
"github.com/docker/docker/runconfig" |
| 24 | 25 |
"github.com/docker/docker/utils" |
| 25 | 26 |
) |
| ... | ... |
@@ -173,7 +174,14 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine {
|
| 173 | 173 |
eng := engine.New() |
| 174 | 174 |
eng.Logging = false |
| 175 | 175 |
// Load default plugins |
| 176 |
- builtins.Register(eng) |
|
| 176 |
+ if err := builtins.Register(eng); err != nil {
|
|
| 177 |
+ t.Fatal(err) |
|
| 178 |
+ } |
|
| 179 |
+ // load registry service |
|
| 180 |
+ if err := registry.NewService(nil).Install(eng); err != nil {
|
|
| 181 |
+ t.Fatal(err) |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 177 | 184 |
// (This is manually copied and modified from main() until we have a more generic plugin system) |
| 178 | 185 |
cfg := &daemon.Config{
|
| 179 | 186 |
Root: root, |
| ... | ... |
@@ -3,7 +3,6 @@ package opts |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"net" |
| 6 |
- "net/url" |
|
| 7 | 6 |
"os" |
| 8 | 7 |
"path" |
| 9 | 8 |
"regexp" |
| ... | ... |
@@ -39,10 +38,6 @@ func IPVar(value *net.IP, names []string, defaultValue, usage string) {
|
| 39 | 39 |
flag.Var(NewIpOpt(value, defaultValue), names, usage) |
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
-func MirrorListVar(values *[]string, names []string, usage string) {
|
|
| 43 |
- flag.Var(newListOptsRef(values, ValidateMirror), names, usage) |
|
| 44 |
-} |
|
| 45 |
- |
|
| 46 | 42 |
func LabelListVar(values *[]string, names []string, usage string) {
|
| 47 | 43 |
flag.Var(newListOptsRef(values, ValidateLabel), names, usage) |
| 48 | 44 |
} |
| ... | ... |
@@ -127,6 +122,7 @@ func (opts *ListOpts) Len() int {
|
| 127 | 127 |
|
| 128 | 128 |
// Validators |
| 129 | 129 |
type ValidatorFctType func(val string) (string, error) |
| 130 |
+type ValidatorFctListType func(val string) ([]string, error) |
|
| 130 | 131 |
|
| 131 | 132 |
func ValidateAttach(val string) (string, error) {
|
| 132 | 133 |
s := strings.ToLower(val) |
| ... | ... |
@@ -214,24 +210,6 @@ func ValidateExtraHost(val string) (string, error) {
|
| 214 | 214 |
return val, nil |
| 215 | 215 |
} |
| 216 | 216 |
|
| 217 |
-// Validates an HTTP(S) registry mirror |
|
| 218 |
-func ValidateMirror(val string) (string, error) {
|
|
| 219 |
- uri, err := url.Parse(val) |
|
| 220 |
- if err != nil {
|
|
| 221 |
- return "", fmt.Errorf("%s is not a valid URI", val)
|
|
| 222 |
- } |
|
| 223 |
- |
|
| 224 |
- if uri.Scheme != "http" && uri.Scheme != "https" {
|
|
| 225 |
- return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
|
|
| 226 |
- } |
|
| 227 |
- |
|
| 228 |
- if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
|
|
| 229 |
- return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
|
|
| 230 |
- } |
|
| 231 |
- |
|
| 232 |
- return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
|
|
| 233 |
-} |
|
| 234 |
- |
|
| 235 | 217 |
func ValidateLabel(val string) (string, error) {
|
| 236 | 218 |
if strings.Count(val, "=") != 1 {
|
| 237 | 219 |
return "", fmt.Errorf("bad attribute format: %s", val)
|
| ... | ... |
@@ -30,7 +30,23 @@ func TestValidateIPAddress(t *testing.T) {
|
| 30 | 30 |
func TestListOpts(t *testing.T) {
|
| 31 | 31 |
o := NewListOpts(nil) |
| 32 | 32 |
o.Set("foo")
|
| 33 |
- o.String() |
|
| 33 |
+ if o.String() != "[foo]" {
|
|
| 34 |
+ t.Errorf("%s != [foo]", o.String())
|
|
| 35 |
+ } |
|
| 36 |
+ o.Set("bar")
|
|
| 37 |
+ if o.Len() != 2 {
|
|
| 38 |
+ t.Errorf("%d != 2", o.Len())
|
|
| 39 |
+ } |
|
| 40 |
+ if !o.Get("bar") {
|
|
| 41 |
+ t.Error("o.Get(\"bar\") == false")
|
|
| 42 |
+ } |
|
| 43 |
+ if o.Get("baz") {
|
|
| 44 |
+ t.Error("o.Get(\"baz\") == true")
|
|
| 45 |
+ } |
|
| 46 |
+ o.Delete("foo")
|
|
| 47 |
+ if o.String() != "[bar]" {
|
|
| 48 |
+ t.Errorf("%s != [bar]", o.String())
|
|
| 49 |
+ } |
|
| 34 | 50 |
} |
| 35 | 51 |
|
| 36 | 52 |
func TestValidateDnsSearch(t *testing.T) {
|
| ... | ... |
@@ -7,7 +7,6 @@ import ( |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"io/ioutil" |
| 9 | 9 |
"net/http" |
| 10 |
- "net/url" |
|
| 11 | 10 |
"os" |
| 12 | 11 |
"path" |
| 13 | 12 |
"strings" |
| ... | ... |
@@ -18,27 +17,12 @@ import ( |
| 18 | 18 |
const ( |
| 19 | 19 |
// Where we store the config file |
| 20 | 20 |
CONFIGFILE = ".dockercfg" |
| 21 |
- |
|
| 22 |
- // Only used for user auth + account creation |
|
| 23 |
- INDEXSERVER = "https://index.docker.io/v1/" |
|
| 24 |
- REGISTRYSERVER = "https://registry-1.docker.io/v1/" |
|
| 25 |
- |
|
| 26 |
- // INDEXSERVER = "https://registry-stage.hub.docker.com/v1/" |
|
| 27 | 21 |
) |
| 28 | 22 |
|
| 29 | 23 |
var ( |
| 30 | 24 |
ErrConfigFileMissing = errors.New("The Auth config file is missing")
|
| 31 |
- IndexServerURL *url.URL |
|
| 32 | 25 |
) |
| 33 | 26 |
|
| 34 |
-func init() {
|
|
| 35 |
- url, err := url.Parse(INDEXSERVER) |
|
| 36 |
- if err != nil {
|
|
| 37 |
- panic(err) |
|
| 38 |
- } |
|
| 39 |
- IndexServerURL = url |
|
| 40 |
-} |
|
| 41 |
- |
|
| 42 | 27 |
type AuthConfig struct {
|
| 43 | 28 |
Username string `json:"username,omitempty"` |
| 44 | 29 |
Password string `json:"password,omitempty"` |
| ... | ... |
@@ -52,10 +36,6 @@ type ConfigFile struct {
|
| 52 | 52 |
rootPath string |
| 53 | 53 |
} |
| 54 | 54 |
|
| 55 |
-func IndexServerAddress() string {
|
|
| 56 |
- return INDEXSERVER |
|
| 57 |
-} |
|
| 58 |
- |
|
| 59 | 55 |
// create a base64 encoded auth string to store in config |
| 60 | 56 |
func encodeAuth(authConfig *AuthConfig) string {
|
| 61 | 57 |
authStr := authConfig.Username + ":" + authConfig.Password |
| ... | ... |
@@ -118,6 +98,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
|
| 118 | 118 |
} |
| 119 | 119 |
authConfig.Email = origEmail[1] |
| 120 | 120 |
authConfig.ServerAddress = IndexServerAddress() |
| 121 |
+ // *TODO: Switch to using IndexServerName() instead? |
|
| 121 | 122 |
configFile.Configs[IndexServerAddress()] = authConfig |
| 122 | 123 |
} else {
|
| 123 | 124 |
for k, authConfig := range configFile.Configs {
|
| ... | ... |
@@ -181,7 +162,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e |
| 181 | 181 |
) |
| 182 | 182 |
|
| 183 | 183 |
if serverAddress == "" {
|
| 184 |
- serverAddress = IndexServerAddress() |
|
| 184 |
+ return "", fmt.Errorf("Server Error: Server Address not set.")
|
|
| 185 | 185 |
} |
| 186 | 186 |
|
| 187 | 187 |
loginAgainstOfficialIndex := serverAddress == IndexServerAddress() |
| ... | ... |
@@ -213,6 +194,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e |
| 213 | 213 |
status = "Account created. Please use the confirmation link we sent" + |
| 214 | 214 |
" to your e-mail to activate it." |
| 215 | 215 |
} else {
|
| 216 |
+ // *TODO: Use registry configuration to determine what this says, if anything? |
|
| 216 | 217 |
status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." |
| 217 | 218 |
} |
| 218 | 219 |
} else if reqStatusCode == 400 {
|
| ... | ... |
@@ -236,6 +218,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e |
| 236 | 236 |
if loginAgainstOfficialIndex {
|
| 237 | 237 |
return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
|
| 238 | 238 |
} |
| 239 |
+ // *TODO: Use registry configuration to determine what this says, if anything? |
|
| 239 | 240 |
return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
|
| 240 | 241 |
} |
| 241 | 242 |
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 |
| 271 | 271 |
} |
| 272 | 272 |
|
| 273 | 273 |
// this method matches a auth configuration to a server address or a url |
| 274 |
-func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
|
|
| 275 |
- if hostname == IndexServerAddress() || len(hostname) == 0 {
|
|
| 276 |
- // default to the index server |
|
| 277 |
- return config.Configs[IndexServerAddress()] |
|
| 278 |
- } |
|
| 279 |
- |
|
| 274 |
+func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
|
|
| 275 |
+ configKey := index.GetAuthConfigKey() |
|
| 280 | 276 |
// First try the happy case |
| 281 |
- if c, found := config.Configs[hostname]; found {
|
|
| 277 |
+ if c, found := config.Configs[configKey]; found || index.Official {
|
|
| 282 | 278 |
return c |
| 283 | 279 |
} |
| 284 | 280 |
|
| ... | ... |
@@ -297,9 +276,8 @@ func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
|
| 297 | 297 |
|
| 298 | 298 |
// Maybe they have a legacy config file, we will iterate the keys converting |
| 299 | 299 |
// them to the new format and testing |
| 300 |
- normalizedHostename := convertToHostname(hostname) |
|
| 301 | 300 |
for registry, config := range config.Configs {
|
| 302 |
- if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename {
|
|
| 301 |
+ if configKey == convertToHostname(registry) {
|
|
| 303 | 302 |
return config |
| 304 | 303 |
} |
| 305 | 304 |
} |
| ... | ... |
@@ -81,12 +81,20 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
|
| 81 | 81 |
} |
| 82 | 82 |
defer os.RemoveAll(configFile.rootPath) |
| 83 | 83 |
|
| 84 |
- for _, registry := range []string{"", IndexServerAddress()} {
|
|
| 85 |
- resolved := configFile.ResolveAuthConfig(registry) |
|
| 86 |
- if resolved != configFile.Configs[IndexServerAddress()] {
|
|
| 87 |
- t.Fail() |
|
| 88 |
- } |
|
| 84 |
+ indexConfig := configFile.Configs[IndexServerAddress()] |
|
| 85 |
+ |
|
| 86 |
+ officialIndex := &IndexInfo{
|
|
| 87 |
+ Official: true, |
|
| 88 |
+ } |
|
| 89 |
+ privateIndex := &IndexInfo{
|
|
| 90 |
+ Official: false, |
|
| 89 | 91 |
} |
| 92 |
+ |
|
| 93 |
+ resolved := configFile.ResolveAuthConfig(officialIndex) |
|
| 94 |
+ assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()") |
|
| 95 |
+ |
|
| 96 |
+ resolved = configFile.ResolveAuthConfig(privateIndex) |
|
| 97 |
+ assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()") |
|
| 90 | 98 |
} |
| 91 | 99 |
|
| 92 | 100 |
func TestResolveAuthConfigFullURL(t *testing.T) {
|
| ... | ... |
@@ -106,18 +114,27 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
| 106 | 106 |
Password: "bar-pass", |
| 107 | 107 |
Email: "bar@example.com", |
| 108 | 108 |
} |
| 109 |
- configFile.Configs["https://registry.example.com/v1/"] = registryAuth |
|
| 110 |
- configFile.Configs["http://localhost:8000/v1/"] = localAuth |
|
| 111 |
- configFile.Configs["registry.com"] = registryAuth |
|
| 109 |
+ officialAuth := AuthConfig{
|
|
| 110 |
+ Username: "baz-user", |
|
| 111 |
+ Password: "baz-pass", |
|
| 112 |
+ Email: "baz@example.com", |
|
| 113 |
+ } |
|
| 114 |
+ configFile.Configs[IndexServerAddress()] = officialAuth |
|
| 115 |
+ |
|
| 116 |
+ expectedAuths := map[string]AuthConfig{
|
|
| 117 |
+ "registry.example.com": registryAuth, |
|
| 118 |
+ "localhost:8000": localAuth, |
|
| 119 |
+ "registry.com": localAuth, |
|
| 120 |
+ } |
|
| 112 | 121 |
|
| 113 | 122 |
validRegistries := map[string][]string{
|
| 114 |
- "https://registry.example.com/v1/": {
|
|
| 123 |
+ "registry.example.com": {
|
|
| 115 | 124 |
"https://registry.example.com/v1/", |
| 116 | 125 |
"http://registry.example.com/v1/", |
| 117 | 126 |
"registry.example.com", |
| 118 | 127 |
"registry.example.com/v1/", |
| 119 | 128 |
}, |
| 120 |
- "http://localhost:8000/v1/": {
|
|
| 129 |
+ "localhost:8000": {
|
|
| 121 | 130 |
"https://localhost:8000/v1/", |
| 122 | 131 |
"http://localhost:8000/v1/", |
| 123 | 132 |
"localhost:8000", |
| ... | ... |
@@ -132,18 +149,24 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
|
| 132 | 132 |
} |
| 133 | 133 |
|
| 134 | 134 |
for configKey, registries := range validRegistries {
|
| 135 |
+ configured, ok := expectedAuths[configKey] |
|
| 136 |
+ if !ok || configured.Email == "" {
|
|
| 137 |
+ t.Fatal() |
|
| 138 |
+ } |
|
| 139 |
+ index := &IndexInfo{
|
|
| 140 |
+ Name: configKey, |
|
| 141 |
+ } |
|
| 135 | 142 |
for _, registry := range registries {
|
| 136 |
- var ( |
|
| 137 |
- configured AuthConfig |
|
| 138 |
- ok bool |
|
| 139 |
- ) |
|
| 140 |
- resolved := configFile.ResolveAuthConfig(registry) |
|
| 141 |
- if configured, ok = configFile.Configs[configKey]; !ok {
|
|
| 142 |
- t.Fail() |
|
| 143 |
- } |
|
| 143 |
+ configFile.Configs[registry] = configured |
|
| 144 |
+ resolved := configFile.ResolveAuthConfig(index) |
|
| 144 | 145 |
if resolved.Email != configured.Email {
|
| 145 | 146 |
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
|
| 146 | 147 |
} |
| 148 |
+ delete(configFile.Configs, registry) |
|
| 149 |
+ resolved = configFile.ResolveAuthConfig(index) |
|
| 150 |
+ if resolved.Email == configured.Email {
|
|
| 151 |
+ t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
|
|
| 152 |
+ } |
|
| 147 | 153 |
} |
| 148 | 154 |
} |
| 149 | 155 |
} |
| 150 | 156 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,382 @@ |
| 0 |
+package registry |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "errors" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "net" |
|
| 7 |
+ "net/url" |
|
| 8 |
+ "regexp" |
|
| 9 |
+ "strings" |
|
| 10 |
+ |
|
| 11 |
+ "github.com/docker/docker/opts" |
|
| 12 |
+ flag "github.com/docker/docker/pkg/mflag" |
|
| 13 |
+ "github.com/docker/docker/utils" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+// Options holds command line options. |
|
| 17 |
+type Options struct {
|
|
| 18 |
+ Mirrors opts.ListOpts |
|
| 19 |
+ InsecureRegistries opts.ListOpts |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+const ( |
|
| 23 |
+ // Only used for user auth + account creation |
|
| 24 |
+ INDEXSERVER = "https://index.docker.io/v1/" |
|
| 25 |
+ REGISTRYSERVER = "https://registry-1.docker.io/v1/" |
|
| 26 |
+ INDEXNAME = "docker.io" |
|
| 27 |
+ |
|
| 28 |
+ // INDEXSERVER = "https://registry-stage.hub.docker.com/v1/" |
|
| 29 |
+) |
|
| 30 |
+ |
|
| 31 |
+var ( |
|
| 32 |
+ ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
|
| 33 |
+ emptyServiceConfig = NewServiceConfig(nil) |
|
| 34 |
+ validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`) |
|
| 35 |
+ validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`) |
|
| 36 |
+) |
|
| 37 |
+ |
|
| 38 |
+func IndexServerAddress() string {
|
|
| 39 |
+ return INDEXSERVER |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+func IndexServerName() string {
|
|
| 43 |
+ return INDEXNAME |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// InstallFlags adds command-line options to the top-level flag parser for |
|
| 47 |
+// the current process. |
|
| 48 |
+func (options *Options) InstallFlags() {
|
|
| 49 |
+ options.Mirrors = opts.NewListOpts(ValidateMirror) |
|
| 50 |
+ flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
|
|
| 51 |
+ options.InsecureRegistries = opts.NewListOpts(ValidateIndexName) |
|
| 52 |
+ 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)")
|
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+type netIPNet net.IPNet |
|
| 56 |
+ |
|
| 57 |
+func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
|
|
| 58 |
+ return json.Marshal((*net.IPNet)(ipnet).String()) |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
|
|
| 62 |
+ var ipnet_str string |
|
| 63 |
+ if err = json.Unmarshal(b, &ipnet_str); err == nil {
|
|
| 64 |
+ var cidr *net.IPNet |
|
| 65 |
+ if _, cidr, err = net.ParseCIDR(ipnet_str); err == nil {
|
|
| 66 |
+ *ipnet = netIPNet(*cidr) |
|
| 67 |
+ } |
|
| 68 |
+ } |
|
| 69 |
+ return |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+// ServiceConfig stores daemon registry services configuration. |
|
| 73 |
+type ServiceConfig struct {
|
|
| 74 |
+ InsecureRegistryCIDRs []*netIPNet `json:"InsecureRegistryCIDRs"` |
|
| 75 |
+ IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+// NewServiceConfig returns a new instance of ServiceConfig |
|
| 79 |
+func NewServiceConfig(options *Options) *ServiceConfig {
|
|
| 80 |
+ if options == nil {
|
|
| 81 |
+ options = &Options{
|
|
| 82 |
+ Mirrors: opts.NewListOpts(nil), |
|
| 83 |
+ InsecureRegistries: opts.NewListOpts(nil), |
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ // Localhost is by default considered as an insecure registry |
|
| 88 |
+ // This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker). |
|
| 89 |
+ // |
|
| 90 |
+ // TODO: should we deprecate this once it is easier for people to set up a TLS registry or change |
|
| 91 |
+ // daemon flags on boot2docker? |
|
| 92 |
+ options.InsecureRegistries.Set("127.0.0.0/8")
|
|
| 93 |
+ |
|
| 94 |
+ config := &ServiceConfig{
|
|
| 95 |
+ InsecureRegistryCIDRs: make([]*netIPNet, 0), |
|
| 96 |
+ IndexConfigs: make(map[string]*IndexInfo, 0), |
|
| 97 |
+ } |
|
| 98 |
+ // Split --insecure-registry into CIDR and registry-specific settings. |
|
| 99 |
+ for _, r := range options.InsecureRegistries.GetAll() {
|
|
| 100 |
+ // Check if CIDR was passed to --insecure-registry |
|
| 101 |
+ _, ipnet, err := net.ParseCIDR(r) |
|
| 102 |
+ if err == nil {
|
|
| 103 |
+ // Valid CIDR. |
|
| 104 |
+ config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*netIPNet)(ipnet)) |
|
| 105 |
+ } else {
|
|
| 106 |
+ // Assume `host:port` if not CIDR. |
|
| 107 |
+ config.IndexConfigs[r] = &IndexInfo{
|
|
| 108 |
+ Name: r, |
|
| 109 |
+ Mirrors: make([]string, 0), |
|
| 110 |
+ Secure: false, |
|
| 111 |
+ Official: false, |
|
| 112 |
+ } |
|
| 113 |
+ } |
|
| 114 |
+ } |
|
| 115 |
+ |
|
| 116 |
+ // Configure public registry. |
|
| 117 |
+ config.IndexConfigs[IndexServerName()] = &IndexInfo{
|
|
| 118 |
+ Name: IndexServerName(), |
|
| 119 |
+ Mirrors: options.Mirrors.GetAll(), |
|
| 120 |
+ Secure: true, |
|
| 121 |
+ Official: true, |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ return config |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+// isSecureIndex returns false if the provided indexName is part of the list of insecure registries |
|
| 128 |
+// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. |
|
| 129 |
+// |
|
| 130 |
+// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet. |
|
| 131 |
+// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered |
|
| 132 |
+// insecure. |
|
| 133 |
+// |
|
| 134 |
+// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name |
|
| 135 |
+// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained |
|
| 136 |
+// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element |
|
| 137 |
+// of insecureRegistries. |
|
| 138 |
+func (config *ServiceConfig) isSecureIndex(indexName string) bool {
|
|
| 139 |
+ // Check for configured index, first. This is needed in case isSecureIndex |
|
| 140 |
+ // is called from anything besides NewIndexInfo, in order to honor per-index configurations. |
|
| 141 |
+ if index, ok := config.IndexConfigs[indexName]; ok {
|
|
| 142 |
+ return index.Secure |
|
| 143 |
+ } |
|
| 144 |
+ |
|
| 145 |
+ host, _, err := net.SplitHostPort(indexName) |
|
| 146 |
+ if err != nil {
|
|
| 147 |
+ // assume indexName is of the form `host` without the port and go on. |
|
| 148 |
+ host = indexName |
|
| 149 |
+ } |
|
| 150 |
+ |
|
| 151 |
+ addrs, err := lookupIP(host) |
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ ip := net.ParseIP(host) |
|
| 154 |
+ if ip != nil {
|
|
| 155 |
+ addrs = []net.IP{ip}
|
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ // if ip == nil, then `host` is neither an IP nor it could be looked up, |
|
| 159 |
+ // either because the index is unreachable, or because the index is behind an HTTP proxy. |
|
| 160 |
+ // So, len(addrs) == 0 and we're not aborting. |
|
| 161 |
+ } |
|
| 162 |
+ |
|
| 163 |
+ // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined. |
|
| 164 |
+ for _, addr := range addrs {
|
|
| 165 |
+ for _, ipnet := range config.InsecureRegistryCIDRs {
|
|
| 166 |
+ // check if the addr falls in the subnet |
|
| 167 |
+ if (*net.IPNet)(ipnet).Contains(addr) {
|
|
| 168 |
+ return false |
|
| 169 |
+ } |
|
| 170 |
+ } |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ return true |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+// ValidateMirror validates an HTTP(S) registry mirror |
|
| 177 |
+func ValidateMirror(val string) (string, error) {
|
|
| 178 |
+ uri, err := url.Parse(val) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return "", fmt.Errorf("%s is not a valid URI", val)
|
|
| 181 |
+ } |
|
| 182 |
+ |
|
| 183 |
+ if uri.Scheme != "http" && uri.Scheme != "https" {
|
|
| 184 |
+ return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
|
|
| 185 |
+ } |
|
| 186 |
+ |
|
| 187 |
+ if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
|
|
| 188 |
+ return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
|
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 191 |
+ return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
|
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+// ValidateIndexName validates an index name. |
|
| 195 |
+func ValidateIndexName(val string) (string, error) {
|
|
| 196 |
+ // 'index.docker.io' => 'docker.io' |
|
| 197 |
+ if val == "index."+IndexServerName() {
|
|
| 198 |
+ val = IndexServerName() |
|
| 199 |
+ } |
|
| 200 |
+ // *TODO: Check if valid hostname[:port]/ip[:port]? |
|
| 201 |
+ return val, nil |
|
| 202 |
+} |
|
| 203 |
+ |
|
| 204 |
+func validateRemoteName(remoteName string) error {
|
|
| 205 |
+ var ( |
|
| 206 |
+ namespace string |
|
| 207 |
+ name string |
|
| 208 |
+ ) |
|
| 209 |
+ nameParts := strings.SplitN(remoteName, "/", 2) |
|
| 210 |
+ if len(nameParts) < 2 {
|
|
| 211 |
+ namespace = "library" |
|
| 212 |
+ name = nameParts[0] |
|
| 213 |
+ |
|
| 214 |
+ // the repository name must not be a valid image ID |
|
| 215 |
+ if err := utils.ValidateID(name); err == nil {
|
|
| 216 |
+ return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
|
|
| 217 |
+ } |
|
| 218 |
+ } else {
|
|
| 219 |
+ namespace = nameParts[0] |
|
| 220 |
+ name = nameParts[1] |
|
| 221 |
+ } |
|
| 222 |
+ if !validNamespaceChars.MatchString(namespace) {
|
|
| 223 |
+ return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
|
|
| 224 |
+ } |
|
| 225 |
+ if len(namespace) < 4 || len(namespace) > 30 {
|
|
| 226 |
+ return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
|
|
| 227 |
+ } |
|
| 228 |
+ if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
|
|
| 229 |
+ return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
|
|
| 230 |
+ } |
|
| 231 |
+ if strings.Contains(namespace, "--") {
|
|
| 232 |
+ return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
|
|
| 233 |
+ } |
|
| 234 |
+ if !validRepo.MatchString(name) {
|
|
| 235 |
+ return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
|
|
| 236 |
+ } |
|
| 237 |
+ return nil |
|
| 238 |
+} |
|
| 239 |
+ |
|
| 240 |
+func validateNoSchema(reposName string) error {
|
|
| 241 |
+ if strings.Contains(reposName, "://") {
|
|
| 242 |
+ // It cannot contain a scheme! |
|
| 243 |
+ return ErrInvalidRepositoryName |
|
| 244 |
+ } |
|
| 245 |
+ return nil |
|
| 246 |
+} |
|
| 247 |
+ |
|
| 248 |
+// ValidateRepositoryName validates a repository name |
|
| 249 |
+func ValidateRepositoryName(reposName string) error {
|
|
| 250 |
+ var err error |
|
| 251 |
+ if err = validateNoSchema(reposName); err != nil {
|
|
| 252 |
+ return err |
|
| 253 |
+ } |
|
| 254 |
+ indexName, remoteName := splitReposName(reposName) |
|
| 255 |
+ if _, err = ValidateIndexName(indexName); err != nil {
|
|
| 256 |
+ return err |
|
| 257 |
+ } |
|
| 258 |
+ return validateRemoteName(remoteName) |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+// NewIndexInfo returns IndexInfo configuration from indexName |
|
| 262 |
+func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
|
|
| 263 |
+ var err error |
|
| 264 |
+ indexName, err = ValidateIndexName(indexName) |
|
| 265 |
+ if err != nil {
|
|
| 266 |
+ return nil, err |
|
| 267 |
+ } |
|
| 268 |
+ |
|
| 269 |
+ // Return any configured index info, first. |
|
| 270 |
+ if index, ok := config.IndexConfigs[indexName]; ok {
|
|
| 271 |
+ return index, nil |
|
| 272 |
+ } |
|
| 273 |
+ |
|
| 274 |
+ // Construct a non-configured index info. |
|
| 275 |
+ index := &IndexInfo{
|
|
| 276 |
+ Name: indexName, |
|
| 277 |
+ Mirrors: make([]string, 0), |
|
| 278 |
+ Official: false, |
|
| 279 |
+ } |
|
| 280 |
+ index.Secure = config.isSecureIndex(indexName) |
|
| 281 |
+ return index, nil |
|
| 282 |
+} |
|
| 283 |
+ |
|
| 284 |
+// GetAuthConfigKey special-cases using the full index address of the official |
|
| 285 |
+// index as the AuthConfig key, and uses the (host)name[:port] for private indexes. |
|
| 286 |
+func (index *IndexInfo) GetAuthConfigKey() string {
|
|
| 287 |
+ if index.Official {
|
|
| 288 |
+ return IndexServerAddress() |
|
| 289 |
+ } |
|
| 290 |
+ return index.Name |
|
| 291 |
+} |
|
| 292 |
+ |
|
| 293 |
+// splitReposName breaks a reposName into an index name and remote name |
|
| 294 |
+func splitReposName(reposName string) (string, string) {
|
|
| 295 |
+ nameParts := strings.SplitN(reposName, "/", 2) |
|
| 296 |
+ var indexName, remoteName string |
|
| 297 |
+ if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && |
|
| 298 |
+ !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
|
|
| 299 |
+ // This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
|
| 300 |
+ // 'docker.io' |
|
| 301 |
+ indexName = IndexServerName() |
|
| 302 |
+ remoteName = reposName |
|
| 303 |
+ } else {
|
|
| 304 |
+ indexName = nameParts[0] |
|
| 305 |
+ remoteName = nameParts[1] |
|
| 306 |
+ } |
|
| 307 |
+ return indexName, remoteName |
|
| 308 |
+} |
|
| 309 |
+ |
|
| 310 |
+// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo |
|
| 311 |
+func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInfo, error) {
|
|
| 312 |
+ if err := validateNoSchema(reposName); err != nil {
|
|
| 313 |
+ return nil, err |
|
| 314 |
+ } |
|
| 315 |
+ |
|
| 316 |
+ indexName, remoteName := splitReposName(reposName) |
|
| 317 |
+ if err := validateRemoteName(remoteName); err != nil {
|
|
| 318 |
+ return nil, err |
|
| 319 |
+ } |
|
| 320 |
+ |
|
| 321 |
+ repoInfo := &RepositoryInfo{
|
|
| 322 |
+ RemoteName: remoteName, |
|
| 323 |
+ } |
|
| 324 |
+ |
|
| 325 |
+ var err error |
|
| 326 |
+ repoInfo.Index, err = config.NewIndexInfo(indexName) |
|
| 327 |
+ if err != nil {
|
|
| 328 |
+ return nil, err |
|
| 329 |
+ } |
|
| 330 |
+ |
|
| 331 |
+ if repoInfo.Index.Official {
|
|
| 332 |
+ normalizedName := repoInfo.RemoteName |
|
| 333 |
+ if strings.HasPrefix(normalizedName, "library/") {
|
|
| 334 |
+ // If pull "library/foo", it's stored locally under "foo" |
|
| 335 |
+ normalizedName = strings.SplitN(normalizedName, "/", 2)[1] |
|
| 336 |
+ } |
|
| 337 |
+ |
|
| 338 |
+ repoInfo.LocalName = normalizedName |
|
| 339 |
+ repoInfo.RemoteName = normalizedName |
|
| 340 |
+ // If the normalized name does not contain a '/' (e.g. "foo") |
|
| 341 |
+ // then it is an official repo. |
|
| 342 |
+ if strings.IndexRune(normalizedName, '/') == -1 {
|
|
| 343 |
+ repoInfo.Official = true |
|
| 344 |
+ // Fix up remote name for official repos. |
|
| 345 |
+ repoInfo.RemoteName = "library/" + normalizedName |
|
| 346 |
+ } |
|
| 347 |
+ |
|
| 348 |
+ // *TODO: Prefix this with 'docker.io/'. |
|
| 349 |
+ repoInfo.CanonicalName = repoInfo.LocalName |
|
| 350 |
+ } else {
|
|
| 351 |
+ // *TODO: Decouple index name from hostname (via registry configuration?) |
|
| 352 |
+ repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName |
|
| 353 |
+ repoInfo.CanonicalName = repoInfo.LocalName |
|
| 354 |
+ } |
|
| 355 |
+ return repoInfo, nil |
|
| 356 |
+} |
|
| 357 |
+ |
|
| 358 |
+// GetSearchTerm special-cases using local name for official index, and |
|
| 359 |
+// remote name for private indexes. |
|
| 360 |
+func (repoInfo *RepositoryInfo) GetSearchTerm() string {
|
|
| 361 |
+ if repoInfo.Index.Official {
|
|
| 362 |
+ return repoInfo.LocalName |
|
| 363 |
+ } |
|
| 364 |
+ return repoInfo.RemoteName |
|
| 365 |
+} |
|
| 366 |
+ |
|
| 367 |
+// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but |
|
| 368 |
+// lacks registry configuration. |
|
| 369 |
+func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
|
|
| 370 |
+ return emptyServiceConfig.NewRepositoryInfo(reposName) |
|
| 371 |
+} |
|
| 372 |
+ |
|
| 373 |
+// NormalizeLocalName transforms a repository name into a normalize LocalName |
|
| 374 |
+// Passes through the name without transformation on error (image id, etc) |
|
| 375 |
+func NormalizeLocalName(name string) string {
|
|
| 376 |
+ repoInfo, err := ParseRepositoryInfo(name) |
|
| 377 |
+ if err != nil {
|
|
| 378 |
+ return name |
|
| 379 |
+ } |
|
| 380 |
+ return repoInfo.LocalName |
|
| 381 |
+} |
| 0 | 382 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,49 @@ |
| 0 |
+package registry |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestValidateMirror(t *testing.T) {
|
|
| 7 |
+ valid := []string{
|
|
| 8 |
+ "http://mirror-1.com", |
|
| 9 |
+ "https://mirror-1.com", |
|
| 10 |
+ "http://localhost", |
|
| 11 |
+ "https://localhost", |
|
| 12 |
+ "http://localhost:5000", |
|
| 13 |
+ "https://localhost:5000", |
|
| 14 |
+ "http://127.0.0.1", |
|
| 15 |
+ "https://127.0.0.1", |
|
| 16 |
+ "http://127.0.0.1:5000", |
|
| 17 |
+ "https://127.0.0.1:5000", |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ invalid := []string{
|
|
| 21 |
+ "!invalid!://%as%", |
|
| 22 |
+ "ftp://mirror-1.com", |
|
| 23 |
+ "http://mirror-1.com/", |
|
| 24 |
+ "http://mirror-1.com/?q=foo", |
|
| 25 |
+ "http://mirror-1.com/v1/", |
|
| 26 |
+ "http://mirror-1.com/v1/?q=foo", |
|
| 27 |
+ "http://mirror-1.com/v1/?q=foo#frag", |
|
| 28 |
+ "http://mirror-1.com?q=foo", |
|
| 29 |
+ "https://mirror-1.com#frag", |
|
| 30 |
+ "https://mirror-1.com/", |
|
| 31 |
+ "https://mirror-1.com/#frag", |
|
| 32 |
+ "https://mirror-1.com/v1/", |
|
| 33 |
+ "https://mirror-1.com/v1/#", |
|
| 34 |
+ "https://mirror-1.com?q", |
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ for _, address := range valid {
|
|
| 38 |
+ if ret, err := ValidateMirror(address); err != nil || ret == "" {
|
|
| 39 |
+ t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
|
|
| 40 |
+ } |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ for _, address := range invalid {
|
|
| 44 |
+ if ret, err := ValidateMirror(address); err == nil || ret != "" {
|
|
| 45 |
+ t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
|
|
| 46 |
+ } |
|
| 47 |
+ } |
|
| 48 |
+} |
| ... | ... |
@@ -37,8 +37,9 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
|
| 37 | 37 |
return hostname, DefaultAPIVersion |
| 38 | 38 |
} |
| 39 | 39 |
|
| 40 |
-func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
|
|
| 41 |
- endpoint, err := newEndpoint(hostname, insecureRegistries) |
|
| 40 |
+func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
|
|
| 41 |
+ // *TODO: Allow per-registry configuration of endpoints. |
|
| 42 |
+ endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure) |
|
| 42 | 43 |
if err != nil {
|
| 43 | 44 |
return nil, err |
| 44 | 45 |
} |
| ... | ... |
@@ -49,7 +50,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error |
| 49 | 49 |
|
| 50 | 50 |
//TODO: triggering highland build can be done there without "failing" |
| 51 | 51 |
|
| 52 |
- if endpoint.secure {
|
|
| 52 |
+ if index.Secure {
|
|
| 53 | 53 |
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` |
| 54 | 54 |
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP. |
| 55 | 55 |
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 |
| 68 | 68 |
|
| 69 | 69 |
return endpoint, nil |
| 70 | 70 |
} |
| 71 |
-func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
|
|
| 71 |
+func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
|
|
| 72 | 72 |
var ( |
| 73 | 73 |
endpoint = Endpoint{}
|
| 74 | 74 |
trimmedHostname string |
| ... | ... |
@@ -82,13 +83,14 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error |
| 82 | 82 |
if err != nil {
|
| 83 | 83 |
return nil, err |
| 84 | 84 |
} |
| 85 |
- endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries) |
|
| 86 |
- if err != nil {
|
|
| 87 |
- return nil, err |
|
| 88 |
- } |
|
| 85 |
+ endpoint.secure = secure |
|
| 89 | 86 |
return &endpoint, nil |
| 90 | 87 |
} |
| 91 | 88 |
|
| 89 |
+func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
|
|
| 90 |
+ return NewEndpoint(repoInfo.Index) |
|
| 91 |
+} |
|
| 92 |
+ |
|
| 92 | 93 |
type Endpoint struct {
|
| 93 | 94 |
URL *url.URL |
| 94 | 95 |
Version APIVersion |
| ... | ... |
@@ -155,63 +157,3 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
|
| 155 | 155 |
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
|
| 156 | 156 |
return info, nil |
| 157 | 157 |
} |
| 158 |
- |
|
| 159 |
-// isSecure returns false if the provided hostname is part of the list of insecure registries. |
|
| 160 |
-// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. |
|
| 161 |
-// |
|
| 162 |
-// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet. |
|
| 163 |
-// If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered |
|
| 164 |
-// insecure. |
|
| 165 |
-// |
|
| 166 |
-// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name |
|
| 167 |
-// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained |
|
| 168 |
-// in a subnet. If the resolving is not successful, isSecure will only try to match hostname to any element |
|
| 169 |
-// of insecureRegistries. |
|
| 170 |
-func isSecure(hostname string, insecureRegistries []string) (bool, error) {
|
|
| 171 |
- if hostname == IndexServerURL.Host {
|
|
| 172 |
- return true, nil |
|
| 173 |
- } |
|
| 174 |
- |
|
| 175 |
- host, _, err := net.SplitHostPort(hostname) |
|
| 176 |
- if err != nil {
|
|
| 177 |
- // assume hostname is of the form `host` without the port and go on. |
|
| 178 |
- host = hostname |
|
| 179 |
- } |
|
| 180 |
- addrs, err := lookupIP(host) |
|
| 181 |
- if err != nil {
|
|
| 182 |
- ip := net.ParseIP(host) |
|
| 183 |
- if ip != nil {
|
|
| 184 |
- addrs = []net.IP{ip}
|
|
| 185 |
- } |
|
| 186 |
- |
|
| 187 |
- // if ip == nil, then `host` is neither an IP nor it could be looked up, |
|
| 188 |
- // either because the index is unreachable, or because the index is behind an HTTP proxy. |
|
| 189 |
- // So, len(addrs) == 0 and we're not aborting. |
|
| 190 |
- } |
|
| 191 |
- |
|
| 192 |
- for _, r := range insecureRegistries {
|
|
| 193 |
- if hostname == r {
|
|
| 194 |
- // hostname matches insecure registry |
|
| 195 |
- return false, nil |
|
| 196 |
- } |
|
| 197 |
- |
|
| 198 |
- // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined. |
|
| 199 |
- for _, addr := range addrs {
|
|
| 200 |
- |
|
| 201 |
- // now assume a CIDR was passed to --insecure-registry |
|
| 202 |
- _, ipnet, err := net.ParseCIDR(r) |
|
| 203 |
- if err != nil {
|
|
| 204 |
- // if we could not parse it as a CIDR, even after removing |
|
| 205 |
- // assume it's not a CIDR and go on with the next candidate |
|
| 206 |
- break |
|
| 207 |
- } |
|
| 208 |
- |
|
| 209 |
- // check if the addr falls in the subnet |
|
| 210 |
- if ipnet.Contains(addr) {
|
|
| 211 |
- return false, nil |
|
| 212 |
- } |
|
| 213 |
- } |
|
| 214 |
- } |
|
| 215 |
- |
|
| 216 |
- return true, nil |
|
| 217 |
-} |
| ... | ... |
@@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
|
| 12 | 12 |
{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
|
| 13 | 13 |
} |
| 14 | 14 |
for _, td := range testData {
|
| 15 |
- e, err := newEndpoint(td.str, insecureRegistries) |
|
| 15 |
+ e, err := newEndpoint(td.str, false) |
|
| 16 | 16 |
if err != nil {
|
| 17 | 17 |
t.Errorf("%q: %s", td.str, err)
|
| 18 | 18 |
} |
| ... | ... |
@@ -10,7 +10,6 @@ import ( |
| 10 | 10 |
"net/http" |
| 11 | 11 |
"os" |
| 12 | 12 |
"path" |
| 13 |
- "regexp" |
|
| 14 | 13 |
"strings" |
| 15 | 14 |
"time" |
| 16 | 15 |
|
| ... | ... |
@@ -19,12 +18,9 @@ import ( |
| 19 | 19 |
) |
| 20 | 20 |
|
| 21 | 21 |
var ( |
| 22 |
- ErrAlreadyExists = errors.New("Image already exists")
|
|
| 23 |
- ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
|
|
| 24 |
- ErrDoesNotExist = errors.New("Image does not exist")
|
|
| 25 |
- errLoginRequired = errors.New("Authentication is required.")
|
|
| 26 |
- validNamespaceChars = regexp.MustCompile(`^([a-z0-9-_]*)$`) |
|
| 27 |
- validRepo = regexp.MustCompile(`^([a-z0-9-_.]+)$`) |
|
| 22 |
+ ErrAlreadyExists = errors.New("Image already exists")
|
|
| 23 |
+ ErrDoesNotExist = errors.New("Image does not exist")
|
|
| 24 |
+ errLoginRequired = errors.New("Authentication is required.")
|
|
| 28 | 25 |
) |
| 29 | 26 |
|
| 30 | 27 |
type TimeoutType uint32 |
| ... | ... |
@@ -160,67 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur |
| 160 | 160 |
return res, client, err |
| 161 | 161 |
} |
| 162 | 162 |
|
| 163 |
-func validateRepositoryName(repositoryName string) error {
|
|
| 164 |
- var ( |
|
| 165 |
- namespace string |
|
| 166 |
- name string |
|
| 167 |
- ) |
|
| 168 |
- nameParts := strings.SplitN(repositoryName, "/", 2) |
|
| 169 |
- if len(nameParts) < 2 {
|
|
| 170 |
- namespace = "library" |
|
| 171 |
- name = nameParts[0] |
|
| 172 |
- |
|
| 173 |
- // the repository name must not be a valid image ID |
|
| 174 |
- if err := utils.ValidateID(name); err == nil {
|
|
| 175 |
- return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
|
|
| 176 |
- } |
|
| 177 |
- } else {
|
|
| 178 |
- namespace = nameParts[0] |
|
| 179 |
- name = nameParts[1] |
|
| 180 |
- } |
|
| 181 |
- if !validNamespaceChars.MatchString(namespace) {
|
|
| 182 |
- return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
|
|
| 183 |
- } |
|
| 184 |
- if len(namespace) < 4 || len(namespace) > 30 {
|
|
| 185 |
- return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
|
|
| 186 |
- } |
|
| 187 |
- if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
|
|
| 188 |
- return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
|
|
| 189 |
- } |
|
| 190 |
- if strings.Contains(namespace, "--") {
|
|
| 191 |
- return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
|
|
| 192 |
- } |
|
| 193 |
- if !validRepo.MatchString(name) {
|
|
| 194 |
- return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
|
|
| 195 |
- } |
|
| 196 |
- return nil |
|
| 197 |
-} |
|
| 198 |
- |
|
| 199 |
-// Resolves a repository name to a hostname + name |
|
| 200 |
-func ResolveRepositoryName(reposName string) (string, string, error) {
|
|
| 201 |
- if strings.Contains(reposName, "://") {
|
|
| 202 |
- // It cannot contain a scheme! |
|
| 203 |
- return "", "", ErrInvalidRepositoryName |
|
| 204 |
- } |
|
| 205 |
- nameParts := strings.SplitN(reposName, "/", 2) |
|
| 206 |
- if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") && |
|
| 207 |
- nameParts[0] != "localhost") {
|
|
| 208 |
- // This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
|
| 209 |
- err := validateRepositoryName(reposName) |
|
| 210 |
- return IndexServerAddress(), reposName, err |
|
| 211 |
- } |
|
| 212 |
- hostname := nameParts[0] |
|
| 213 |
- reposName = nameParts[1] |
|
| 214 |
- if strings.Contains(hostname, "index.docker.io") {
|
|
| 215 |
- return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
|
|
| 216 |
- } |
|
| 217 |
- if err := validateRepositoryName(reposName); err != nil {
|
|
| 218 |
- return "", "", err |
|
| 219 |
- } |
|
| 220 |
- |
|
| 221 |
- return hostname, reposName, nil |
|
| 222 |
-} |
|
| 223 |
- |
|
| 224 | 163 |
func trustedLocation(req *http.Request) bool {
|
| 225 | 164 |
var ( |
| 226 | 165 |
trusteds = []string{"docker.com", "docker.io"}
|
| ... | ... |
@@ -15,15 +15,16 @@ import ( |
| 15 | 15 |
"testing" |
| 16 | 16 |
"time" |
| 17 | 17 |
|
| 18 |
+ "github.com/docker/docker/opts" |
|
| 18 | 19 |
"github.com/gorilla/mux" |
| 19 | 20 |
|
| 20 | 21 |
log "github.com/Sirupsen/logrus" |
| 21 | 22 |
) |
| 22 | 23 |
|
| 23 | 24 |
var ( |
| 24 |
- testHTTPServer *httptest.Server |
|
| 25 |
- insecureRegistries []string |
|
| 26 |
- testLayers = map[string]map[string]string{
|
|
| 25 |
+ testHTTPServer *httptest.Server |
|
| 26 |
+ testHTTPSServer *httptest.Server |
|
| 27 |
+ testLayers = map[string]map[string]string{
|
|
| 27 | 28 |
"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
|
| 28 | 29 |
"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
|
| 29 | 30 |
"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00", |
| ... | ... |
@@ -86,6 +87,7 @@ var ( |
| 86 | 86 |
"": {net.ParseIP("0.0.0.0")},
|
| 87 | 87 |
"localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
|
| 88 | 88 |
"example.com": {net.ParseIP("42.42.42.42")},
|
| 89 |
+ "other.com": {net.ParseIP("43.43.43.43")},
|
|
| 89 | 90 |
} |
| 90 | 91 |
) |
| 91 | 92 |
|
| ... | ... |
@@ -108,11 +110,7 @@ func init() {
|
| 108 | 108 |
r.HandleFunc("/v2/version", handlerGetPing).Methods("GET")
|
| 109 | 109 |
|
| 110 | 110 |
testHTTPServer = httptest.NewServer(handlerAccessLog(r)) |
| 111 |
- URL, err := url.Parse(testHTTPServer.URL) |
|
| 112 |
- if err != nil {
|
|
| 113 |
- panic(err) |
|
| 114 |
- } |
|
| 115 |
- insecureRegistries = []string{URL.Host}
|
|
| 111 |
+ testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r)) |
|
| 116 | 112 |
|
| 117 | 113 |
// override net.LookupIP |
| 118 | 114 |
lookupIP = func(host string) ([]net.IP, error) {
|
| ... | ... |
@@ -146,6 +144,52 @@ func makeURL(req string) string {
|
| 146 | 146 |
return testHTTPServer.URL + req |
| 147 | 147 |
} |
| 148 | 148 |
|
| 149 |
+func makeHttpsURL(req string) string {
|
|
| 150 |
+ return testHTTPSServer.URL + req |
|
| 151 |
+} |
|
| 152 |
+ |
|
| 153 |
+func makeIndex(req string) *IndexInfo {
|
|
| 154 |
+ index := &IndexInfo{
|
|
| 155 |
+ Name: makeURL(req), |
|
| 156 |
+ } |
|
| 157 |
+ return index |
|
| 158 |
+} |
|
| 159 |
+ |
|
| 160 |
+func makeHttpsIndex(req string) *IndexInfo {
|
|
| 161 |
+ index := &IndexInfo{
|
|
| 162 |
+ Name: makeHttpsURL(req), |
|
| 163 |
+ } |
|
| 164 |
+ return index |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+func makePublicIndex() *IndexInfo {
|
|
| 168 |
+ index := &IndexInfo{
|
|
| 169 |
+ Name: IndexServerAddress(), |
|
| 170 |
+ Secure: true, |
|
| 171 |
+ Official: true, |
|
| 172 |
+ } |
|
| 173 |
+ return index |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceConfig {
|
|
| 177 |
+ options := &Options{
|
|
| 178 |
+ Mirrors: opts.NewListOpts(nil), |
|
| 179 |
+ InsecureRegistries: opts.NewListOpts(nil), |
|
| 180 |
+ } |
|
| 181 |
+ if mirrors != nil {
|
|
| 182 |
+ for _, mirror := range mirrors {
|
|
| 183 |
+ options.Mirrors.Set(mirror) |
|
| 184 |
+ } |
|
| 185 |
+ } |
|
| 186 |
+ if insecure_registries != nil {
|
|
| 187 |
+ for _, insecure_registries := range insecure_registries {
|
|
| 188 |
+ options.InsecureRegistries.Set(insecure_registries) |
|
| 189 |
+ } |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ return NewServiceConfig(options) |
|
| 193 |
+} |
|
| 194 |
+ |
|
| 149 | 195 |
func writeHeaders(w http.ResponseWriter) {
|
| 150 | 196 |
h := w.Header() |
| 151 | 197 |
h.Add("Server", "docker-tests/mock")
|
| ... | ... |
@@ -193,6 +237,40 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
| 193 | 193 |
t.Fatal(message) |
| 194 | 194 |
} |
| 195 | 195 |
|
| 196 |
+func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
|
|
| 197 |
+ if a != b {
|
|
| 198 |
+ return |
|
| 199 |
+ } |
|
| 200 |
+ if len(message) == 0 {
|
|
| 201 |
+ message = fmt.Sprintf("%v == %v", a, b)
|
|
| 202 |
+ } |
|
| 203 |
+ t.Fatal(message) |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+// Similar to assertEqual, but does not stop test |
|
| 207 |
+func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
|
|
| 208 |
+ if a == b {
|
|
| 209 |
+ return |
|
| 210 |
+ } |
|
| 211 |
+ message := fmt.Sprintf("%v != %v", a, b)
|
|
| 212 |
+ if len(messagePrefix) != 0 {
|
|
| 213 |
+ message = messagePrefix + ": " + message |
|
| 214 |
+ } |
|
| 215 |
+ t.Error(message) |
|
| 216 |
+} |
|
| 217 |
+ |
|
| 218 |
+// Similar to assertNotEqual, but does not stop test |
|
| 219 |
+func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
|
|
| 220 |
+ if a != b {
|
|
| 221 |
+ return |
|
| 222 |
+ } |
|
| 223 |
+ message := fmt.Sprintf("%v == %v", a, b)
|
|
| 224 |
+ if len(messagePrefix) != 0 {
|
|
| 225 |
+ message = messagePrefix + ": " + message |
|
| 226 |
+ } |
|
| 227 |
+ t.Error(message) |
|
| 228 |
+} |
|
| 229 |
+ |
|
| 196 | 230 |
func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
|
| 197 | 231 |
writeCookie := func() {
|
| 198 | 232 |
value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
|
| ... | ... |
@@ -271,6 +349,7 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
|
| 271 | 271 |
return |
| 272 | 272 |
} |
| 273 | 273 |
repositoryName := mux.Vars(r)["repository"] |
| 274 |
+ repositoryName = NormalizeLocalName(repositoryName) |
|
| 274 | 275 |
tags, exists := testRepositories[repositoryName] |
| 275 | 276 |
if !exists {
|
| 276 | 277 |
apiError(w, "Repository not found", 404) |
| ... | ... |
@@ -290,6 +369,7 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
|
| 290 | 290 |
} |
| 291 | 291 |
vars := mux.Vars(r) |
| 292 | 292 |
repositoryName := vars["repository"] |
| 293 |
+ repositoryName = NormalizeLocalName(repositoryName) |
|
| 293 | 294 |
tagName := vars["tag"] |
| 294 | 295 |
tags, exists := testRepositories[repositoryName] |
| 295 | 296 |
if !exists {
|
| ... | ... |
@@ -310,6 +390,7 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
|
| 310 | 310 |
} |
| 311 | 311 |
vars := mux.Vars(r) |
| 312 | 312 |
repositoryName := vars["repository"] |
| 313 |
+ repositoryName = NormalizeLocalName(repositoryName) |
|
| 313 | 314 |
tagName := vars["tag"] |
| 314 | 315 |
tags, exists := testRepositories[repositoryName] |
| 315 | 316 |
if !exists {
|
| ... | ... |
@@ -21,7 +21,7 @@ const ( |
| 21 | 21 |
|
| 22 | 22 |
func spawnTestRegistrySession(t *testing.T) *Session {
|
| 23 | 23 |
authConfig := &AuthConfig{}
|
| 24 |
- endpoint, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
|
|
| 24 |
+ endpoint, err := NewEndpoint(makeIndex("/v1/"))
|
|
| 25 | 25 |
if err != nil {
|
| 26 | 26 |
t.Fatal(err) |
| 27 | 27 |
} |
| ... | ... |
@@ -32,16 +32,139 @@ func spawnTestRegistrySession(t *testing.T) *Session {
|
| 32 | 32 |
return r |
| 33 | 33 |
} |
| 34 | 34 |
|
| 35 |
+func TestPublicSession(t *testing.T) {
|
|
| 36 |
+ authConfig := &AuthConfig{}
|
|
| 37 |
+ |
|
| 38 |
+ getSessionDecorators := func(index *IndexInfo) int {
|
|
| 39 |
+ endpoint, err := NewEndpoint(index) |
|
| 40 |
+ if err != nil {
|
|
| 41 |
+ t.Fatal(err) |
|
| 42 |
+ } |
|
| 43 |
+ r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true) |
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ t.Fatal(err) |
|
| 46 |
+ } |
|
| 47 |
+ return len(r.reqFactory.GetDecorators()) |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ decorators := getSessionDecorators(makeIndex("/v1/"))
|
|
| 51 |
+ assertEqual(t, decorators, 0, "Expected no decorator on http session") |
|
| 52 |
+ |
|
| 53 |
+ decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
|
|
| 54 |
+ assertNotEqual(t, decorators, 0, "Expected decorator on https session") |
|
| 55 |
+ |
|
| 56 |
+ decorators = getSessionDecorators(makePublicIndex()) |
|
| 57 |
+ assertEqual(t, decorators, 0, "Expected no decorator on public session") |
|
| 58 |
+} |
|
| 59 |
+ |
|
| 35 | 60 |
func TestPingRegistryEndpoint(t *testing.T) {
|
| 36 |
- ep, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
|
|
| 37 |
- if err != nil {
|
|
| 38 |
- t.Fatal(err) |
|
| 61 |
+ testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
|
|
| 62 |
+ ep, err := NewEndpoint(index) |
|
| 63 |
+ if err != nil {
|
|
| 64 |
+ t.Fatal(err) |
|
| 65 |
+ } |
|
| 66 |
+ regInfo, err := ep.Ping() |
|
| 67 |
+ if err != nil {
|
|
| 68 |
+ t.Fatal(err) |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage) |
|
| 39 | 72 |
} |
| 40 |
- regInfo, err := ep.Ping() |
|
| 41 |
- if err != nil {
|
|
| 42 |
- t.Fatal(err) |
|
| 73 |
+ |
|
| 74 |
+ testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
|
|
| 75 |
+ testPing(makeHttpsIndex("/v1/"), true, "Expected standalone to be true (default)")
|
|
| 76 |
+ testPing(makePublicIndex(), false, "Expected standalone to be false for public index") |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func TestEndpoint(t *testing.T) {
|
|
| 80 |
+ // Simple wrapper to fail test if err != nil |
|
| 81 |
+ expandEndpoint := func(index *IndexInfo) *Endpoint {
|
|
| 82 |
+ endpoint, err := NewEndpoint(index) |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ t.Fatal(err) |
|
| 85 |
+ } |
|
| 86 |
+ return endpoint |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ assertInsecureIndex := func(index *IndexInfo) {
|
|
| 90 |
+ index.Secure = true |
|
| 91 |
+ _, err := NewEndpoint(index) |
|
| 92 |
+ assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index") |
|
| 93 |
+ assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index") |
|
| 94 |
+ index.Secure = false |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ assertSecureIndex := func(index *IndexInfo) {
|
|
| 98 |
+ index.Secure = true |
|
| 99 |
+ _, err := NewEndpoint(index) |
|
| 100 |
+ assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index") |
|
| 101 |
+ assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index") |
|
| 102 |
+ index.Secure = false |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ index := &IndexInfo{}
|
|
| 106 |
+ index.Name = makeURL("/v1/")
|
|
| 107 |
+ endpoint := expandEndpoint(index) |
|
| 108 |
+ assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) |
|
| 109 |
+ if endpoint.Version != APIVersion1 {
|
|
| 110 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 111 |
+ } |
|
| 112 |
+ assertInsecureIndex(index) |
|
| 113 |
+ |
|
| 114 |
+ index.Name = makeURL("")
|
|
| 115 |
+ endpoint = expandEndpoint(index) |
|
| 116 |
+ assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") |
|
| 117 |
+ if endpoint.Version != APIVersion1 {
|
|
| 118 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 119 |
+ } |
|
| 120 |
+ assertInsecureIndex(index) |
|
| 121 |
+ |
|
| 122 |
+ httpURL := makeURL("")
|
|
| 123 |
+ index.Name = strings.SplitN(httpURL, "://", 2)[1] |
|
| 124 |
+ endpoint = expandEndpoint(index) |
|
| 125 |
+ assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/") |
|
| 126 |
+ if endpoint.Version != APIVersion1 {
|
|
| 127 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 128 |
+ } |
|
| 129 |
+ assertInsecureIndex(index) |
|
| 130 |
+ |
|
| 131 |
+ index.Name = makeHttpsURL("/v1/")
|
|
| 132 |
+ endpoint = expandEndpoint(index) |
|
| 133 |
+ assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name) |
|
| 134 |
+ if endpoint.Version != APIVersion1 {
|
|
| 135 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 136 |
+ } |
|
| 137 |
+ assertSecureIndex(index) |
|
| 138 |
+ |
|
| 139 |
+ index.Name = makeHttpsURL("")
|
|
| 140 |
+ endpoint = expandEndpoint(index) |
|
| 141 |
+ assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/") |
|
| 142 |
+ if endpoint.Version != APIVersion1 {
|
|
| 143 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 144 |
+ } |
|
| 145 |
+ assertSecureIndex(index) |
|
| 146 |
+ |
|
| 147 |
+ httpsURL := makeHttpsURL("")
|
|
| 148 |
+ index.Name = strings.SplitN(httpsURL, "://", 2)[1] |
|
| 149 |
+ endpoint = expandEndpoint(index) |
|
| 150 |
+ assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/") |
|
| 151 |
+ if endpoint.Version != APIVersion1 {
|
|
| 152 |
+ t.Fatal("Expected endpoint to be v1")
|
|
| 153 |
+ } |
|
| 154 |
+ assertSecureIndex(index) |
|
| 155 |
+ |
|
| 156 |
+ badEndpoints := []string{
|
|
| 157 |
+ "http://127.0.0.1/v1/", |
|
| 158 |
+ "https://127.0.0.1/v1/", |
|
| 159 |
+ "http://127.0.0.1", |
|
| 160 |
+ "https://127.0.0.1", |
|
| 161 |
+ "127.0.0.1", |
|
| 162 |
+ } |
|
| 163 |
+ for _, address := range badEndpoints {
|
|
| 164 |
+ index.Name = address |
|
| 165 |
+ _, err := NewEndpoint(index) |
|
| 166 |
+ checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint") |
|
| 43 | 167 |
} |
| 44 |
- assertEqual(t, regInfo.Standalone, true, "Expected standalone to be true (default)") |
|
| 45 | 168 |
} |
| 46 | 169 |
|
| 47 | 170 |
func TestGetRemoteHistory(t *testing.T) {
|
| ... | ... |
@@ -156,30 +279,413 @@ func TestPushImageLayerRegistry(t *testing.T) {
|
| 156 | 156 |
} |
| 157 | 157 |
} |
| 158 | 158 |
|
| 159 |
-func TestResolveRepositoryName(t *testing.T) {
|
|
| 160 |
- _, _, err := ResolveRepositoryName("https://github.com/docker/docker")
|
|
| 161 |
- assertEqual(t, err, ErrInvalidRepositoryName, "Expected error invalid repo name") |
|
| 162 |
- ep, repo, err := ResolveRepositoryName("fooo/bar")
|
|
| 163 |
- if err != nil {
|
|
| 164 |
- t.Fatal(err) |
|
| 159 |
+func TestValidateRepositoryName(t *testing.T) {
|
|
| 160 |
+ validRepoNames := []string{
|
|
| 161 |
+ "docker/docker", |
|
| 162 |
+ "library/debian", |
|
| 163 |
+ "debian", |
|
| 164 |
+ "docker.io/docker/docker", |
|
| 165 |
+ "docker.io/library/debian", |
|
| 166 |
+ "docker.io/debian", |
|
| 167 |
+ "index.docker.io/docker/docker", |
|
| 168 |
+ "index.docker.io/library/debian", |
|
| 169 |
+ "index.docker.io/debian", |
|
| 170 |
+ "127.0.0.1:5000/docker/docker", |
|
| 171 |
+ "127.0.0.1:5000/library/debian", |
|
| 172 |
+ "127.0.0.1:5000/debian", |
|
| 173 |
+ "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", |
|
| 174 |
+ } |
|
| 175 |
+ invalidRepoNames := []string{
|
|
| 176 |
+ "https://github.com/docker/docker", |
|
| 177 |
+ "docker/Docker", |
|
| 178 |
+ "docker///docker", |
|
| 179 |
+ "docker.io/docker/Docker", |
|
| 180 |
+ "docker.io/docker///docker", |
|
| 181 |
+ "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", |
|
| 182 |
+ "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", |
|
| 165 | 183 |
} |
| 166 |
- assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address") |
|
| 167 |
- assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar") |
|
| 168 | 184 |
|
| 169 |
- u := makeURL("")[7:]
|
|
| 170 |
- ep, repo, err = ResolveRepositoryName(u + "/private/moonbase") |
|
| 171 |
- if err != nil {
|
|
| 172 |
- t.Fatal(err) |
|
| 185 |
+ for _, name := range invalidRepoNames {
|
|
| 186 |
+ err := ValidateRepositoryName(name) |
|
| 187 |
+ assertNotEqual(t, err, nil, "Expected invalid repo name: "+name) |
|
| 173 | 188 |
} |
| 174 |
- assertEqual(t, ep, u, "Expected endpoint to be "+u) |
|
| 175 |
- assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase") |
|
| 176 | 189 |
|
| 177 |
- ep, repo, err = ResolveRepositoryName("ubuntu-12.04-base")
|
|
| 178 |
- if err != nil {
|
|
| 179 |
- t.Fatal(err) |
|
| 190 |
+ for _, name := range validRepoNames {
|
|
| 191 |
+ err := ValidateRepositoryName(name) |
|
| 192 |
+ assertEqual(t, err, nil, "Expected valid repo name: "+name) |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ err := ValidateRepositoryName(invalidRepoNames[0]) |
|
| 196 |
+ assertEqual(t, err, ErrInvalidRepositoryName, "Expected ErrInvalidRepositoryName: "+invalidRepoNames[0]) |
|
| 197 |
+} |
|
| 198 |
+ |
|
| 199 |
+func TestParseRepositoryInfo(t *testing.T) {
|
|
| 200 |
+ expectedRepoInfos := map[string]RepositoryInfo{
|
|
| 201 |
+ "fooo/bar": {
|
|
| 202 |
+ Index: &IndexInfo{
|
|
| 203 |
+ Name: IndexServerName(), |
|
| 204 |
+ Official: true, |
|
| 205 |
+ }, |
|
| 206 |
+ RemoteName: "fooo/bar", |
|
| 207 |
+ LocalName: "fooo/bar", |
|
| 208 |
+ CanonicalName: "fooo/bar", |
|
| 209 |
+ Official: false, |
|
| 210 |
+ }, |
|
| 211 |
+ "library/ubuntu": {
|
|
| 212 |
+ Index: &IndexInfo{
|
|
| 213 |
+ Name: IndexServerName(), |
|
| 214 |
+ Official: true, |
|
| 215 |
+ }, |
|
| 216 |
+ RemoteName: "library/ubuntu", |
|
| 217 |
+ LocalName: "ubuntu", |
|
| 218 |
+ CanonicalName: "ubuntu", |
|
| 219 |
+ Official: true, |
|
| 220 |
+ }, |
|
| 221 |
+ "nonlibrary/ubuntu": {
|
|
| 222 |
+ Index: &IndexInfo{
|
|
| 223 |
+ Name: IndexServerName(), |
|
| 224 |
+ Official: true, |
|
| 225 |
+ }, |
|
| 226 |
+ RemoteName: "nonlibrary/ubuntu", |
|
| 227 |
+ LocalName: "nonlibrary/ubuntu", |
|
| 228 |
+ CanonicalName: "nonlibrary/ubuntu", |
|
| 229 |
+ Official: false, |
|
| 230 |
+ }, |
|
| 231 |
+ "ubuntu": {
|
|
| 232 |
+ Index: &IndexInfo{
|
|
| 233 |
+ Name: IndexServerName(), |
|
| 234 |
+ Official: true, |
|
| 235 |
+ }, |
|
| 236 |
+ RemoteName: "library/ubuntu", |
|
| 237 |
+ LocalName: "ubuntu", |
|
| 238 |
+ CanonicalName: "ubuntu", |
|
| 239 |
+ Official: true, |
|
| 240 |
+ }, |
|
| 241 |
+ "other/library": {
|
|
| 242 |
+ Index: &IndexInfo{
|
|
| 243 |
+ Name: IndexServerName(), |
|
| 244 |
+ Official: true, |
|
| 245 |
+ }, |
|
| 246 |
+ RemoteName: "other/library", |
|
| 247 |
+ LocalName: "other/library", |
|
| 248 |
+ CanonicalName: "other/library", |
|
| 249 |
+ Official: false, |
|
| 250 |
+ }, |
|
| 251 |
+ "127.0.0.1:8000/private/moonbase": {
|
|
| 252 |
+ Index: &IndexInfo{
|
|
| 253 |
+ Name: "127.0.0.1:8000", |
|
| 254 |
+ Official: false, |
|
| 255 |
+ }, |
|
| 256 |
+ RemoteName: "private/moonbase", |
|
| 257 |
+ LocalName: "127.0.0.1:8000/private/moonbase", |
|
| 258 |
+ CanonicalName: "127.0.0.1:8000/private/moonbase", |
|
| 259 |
+ Official: false, |
|
| 260 |
+ }, |
|
| 261 |
+ "127.0.0.1:8000/privatebase": {
|
|
| 262 |
+ Index: &IndexInfo{
|
|
| 263 |
+ Name: "127.0.0.1:8000", |
|
| 264 |
+ Official: false, |
|
| 265 |
+ }, |
|
| 266 |
+ RemoteName: "privatebase", |
|
| 267 |
+ LocalName: "127.0.0.1:8000/privatebase", |
|
| 268 |
+ CanonicalName: "127.0.0.1:8000/privatebase", |
|
| 269 |
+ Official: false, |
|
| 270 |
+ }, |
|
| 271 |
+ "localhost:8000/private/moonbase": {
|
|
| 272 |
+ Index: &IndexInfo{
|
|
| 273 |
+ Name: "localhost:8000", |
|
| 274 |
+ Official: false, |
|
| 275 |
+ }, |
|
| 276 |
+ RemoteName: "private/moonbase", |
|
| 277 |
+ LocalName: "localhost:8000/private/moonbase", |
|
| 278 |
+ CanonicalName: "localhost:8000/private/moonbase", |
|
| 279 |
+ Official: false, |
|
| 280 |
+ }, |
|
| 281 |
+ "localhost:8000/privatebase": {
|
|
| 282 |
+ Index: &IndexInfo{
|
|
| 283 |
+ Name: "localhost:8000", |
|
| 284 |
+ Official: false, |
|
| 285 |
+ }, |
|
| 286 |
+ RemoteName: "privatebase", |
|
| 287 |
+ LocalName: "localhost:8000/privatebase", |
|
| 288 |
+ CanonicalName: "localhost:8000/privatebase", |
|
| 289 |
+ Official: false, |
|
| 290 |
+ }, |
|
| 291 |
+ "example.com/private/moonbase": {
|
|
| 292 |
+ Index: &IndexInfo{
|
|
| 293 |
+ Name: "example.com", |
|
| 294 |
+ Official: false, |
|
| 295 |
+ }, |
|
| 296 |
+ RemoteName: "private/moonbase", |
|
| 297 |
+ LocalName: "example.com/private/moonbase", |
|
| 298 |
+ CanonicalName: "example.com/private/moonbase", |
|
| 299 |
+ Official: false, |
|
| 300 |
+ }, |
|
| 301 |
+ "example.com/privatebase": {
|
|
| 302 |
+ Index: &IndexInfo{
|
|
| 303 |
+ Name: "example.com", |
|
| 304 |
+ Official: false, |
|
| 305 |
+ }, |
|
| 306 |
+ RemoteName: "privatebase", |
|
| 307 |
+ LocalName: "example.com/privatebase", |
|
| 308 |
+ CanonicalName: "example.com/privatebase", |
|
| 309 |
+ Official: false, |
|
| 310 |
+ }, |
|
| 311 |
+ "example.com:8000/private/moonbase": {
|
|
| 312 |
+ Index: &IndexInfo{
|
|
| 313 |
+ Name: "example.com:8000", |
|
| 314 |
+ Official: false, |
|
| 315 |
+ }, |
|
| 316 |
+ RemoteName: "private/moonbase", |
|
| 317 |
+ LocalName: "example.com:8000/private/moonbase", |
|
| 318 |
+ CanonicalName: "example.com:8000/private/moonbase", |
|
| 319 |
+ Official: false, |
|
| 320 |
+ }, |
|
| 321 |
+ "example.com:8000/privatebase": {
|
|
| 322 |
+ Index: &IndexInfo{
|
|
| 323 |
+ Name: "example.com:8000", |
|
| 324 |
+ Official: false, |
|
| 325 |
+ }, |
|
| 326 |
+ RemoteName: "privatebase", |
|
| 327 |
+ LocalName: "example.com:8000/privatebase", |
|
| 328 |
+ CanonicalName: "example.com:8000/privatebase", |
|
| 329 |
+ Official: false, |
|
| 330 |
+ }, |
|
| 331 |
+ "localhost/private/moonbase": {
|
|
| 332 |
+ Index: &IndexInfo{
|
|
| 333 |
+ Name: "localhost", |
|
| 334 |
+ Official: false, |
|
| 335 |
+ }, |
|
| 336 |
+ RemoteName: "private/moonbase", |
|
| 337 |
+ LocalName: "localhost/private/moonbase", |
|
| 338 |
+ CanonicalName: "localhost/private/moonbase", |
|
| 339 |
+ Official: false, |
|
| 340 |
+ }, |
|
| 341 |
+ "localhost/privatebase": {
|
|
| 342 |
+ Index: &IndexInfo{
|
|
| 343 |
+ Name: "localhost", |
|
| 344 |
+ Official: false, |
|
| 345 |
+ }, |
|
| 346 |
+ RemoteName: "privatebase", |
|
| 347 |
+ LocalName: "localhost/privatebase", |
|
| 348 |
+ CanonicalName: "localhost/privatebase", |
|
| 349 |
+ Official: false, |
|
| 350 |
+ }, |
|
| 351 |
+ IndexServerName() + "/public/moonbase": {
|
|
| 352 |
+ Index: &IndexInfo{
|
|
| 353 |
+ Name: IndexServerName(), |
|
| 354 |
+ Official: true, |
|
| 355 |
+ }, |
|
| 356 |
+ RemoteName: "public/moonbase", |
|
| 357 |
+ LocalName: "public/moonbase", |
|
| 358 |
+ CanonicalName: "public/moonbase", |
|
| 359 |
+ Official: false, |
|
| 360 |
+ }, |
|
| 361 |
+ "index." + IndexServerName() + "/public/moonbase": {
|
|
| 362 |
+ Index: &IndexInfo{
|
|
| 363 |
+ Name: IndexServerName(), |
|
| 364 |
+ Official: true, |
|
| 365 |
+ }, |
|
| 366 |
+ RemoteName: "public/moonbase", |
|
| 367 |
+ LocalName: "public/moonbase", |
|
| 368 |
+ CanonicalName: "public/moonbase", |
|
| 369 |
+ Official: false, |
|
| 370 |
+ }, |
|
| 371 |
+ IndexServerName() + "/public/moonbase": {
|
|
| 372 |
+ Index: &IndexInfo{
|
|
| 373 |
+ Name: IndexServerName(), |
|
| 374 |
+ Official: true, |
|
| 375 |
+ }, |
|
| 376 |
+ RemoteName: "public/moonbase", |
|
| 377 |
+ LocalName: "public/moonbase", |
|
| 378 |
+ CanonicalName: "public/moonbase", |
|
| 379 |
+ Official: false, |
|
| 380 |
+ }, |
|
| 381 |
+ "ubuntu-12.04-base": {
|
|
| 382 |
+ Index: &IndexInfo{
|
|
| 383 |
+ Name: IndexServerName(), |
|
| 384 |
+ Official: true, |
|
| 385 |
+ }, |
|
| 386 |
+ RemoteName: "library/ubuntu-12.04-base", |
|
| 387 |
+ LocalName: "ubuntu-12.04-base", |
|
| 388 |
+ CanonicalName: "ubuntu-12.04-base", |
|
| 389 |
+ Official: true, |
|
| 390 |
+ }, |
|
| 391 |
+ IndexServerName() + "/ubuntu-12.04-base": {
|
|
| 392 |
+ Index: &IndexInfo{
|
|
| 393 |
+ Name: IndexServerName(), |
|
| 394 |
+ Official: true, |
|
| 395 |
+ }, |
|
| 396 |
+ RemoteName: "library/ubuntu-12.04-base", |
|
| 397 |
+ LocalName: "ubuntu-12.04-base", |
|
| 398 |
+ CanonicalName: "ubuntu-12.04-base", |
|
| 399 |
+ Official: true, |
|
| 400 |
+ }, |
|
| 401 |
+ IndexServerName() + "/ubuntu-12.04-base": {
|
|
| 402 |
+ Index: &IndexInfo{
|
|
| 403 |
+ Name: IndexServerName(), |
|
| 404 |
+ Official: true, |
|
| 405 |
+ }, |
|
| 406 |
+ RemoteName: "library/ubuntu-12.04-base", |
|
| 407 |
+ LocalName: "ubuntu-12.04-base", |
|
| 408 |
+ CanonicalName: "ubuntu-12.04-base", |
|
| 409 |
+ Official: true, |
|
| 410 |
+ }, |
|
| 411 |
+ "index." + IndexServerName() + "/ubuntu-12.04-base": {
|
|
| 412 |
+ Index: &IndexInfo{
|
|
| 413 |
+ Name: IndexServerName(), |
|
| 414 |
+ Official: true, |
|
| 415 |
+ }, |
|
| 416 |
+ RemoteName: "library/ubuntu-12.04-base", |
|
| 417 |
+ LocalName: "ubuntu-12.04-base", |
|
| 418 |
+ CanonicalName: "ubuntu-12.04-base", |
|
| 419 |
+ Official: true, |
|
| 420 |
+ }, |
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 423 |
+ for reposName, expectedRepoInfo := range expectedRepoInfos {
|
|
| 424 |
+ repoInfo, err := ParseRepositoryInfo(reposName) |
|
| 425 |
+ if err != nil {
|
|
| 426 |
+ t.Error(err) |
|
| 427 |
+ } else {
|
|
| 428 |
+ checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName) |
|
| 429 |
+ checkEqual(t, repoInfo.RemoteName, expectedRepoInfo.RemoteName, reposName) |
|
| 430 |
+ checkEqual(t, repoInfo.LocalName, expectedRepoInfo.LocalName, reposName) |
|
| 431 |
+ checkEqual(t, repoInfo.CanonicalName, expectedRepoInfo.CanonicalName, reposName) |
|
| 432 |
+ checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName) |
|
| 433 |
+ checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName) |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+} |
|
| 437 |
+ |
|
| 438 |
+func TestNewIndexInfo(t *testing.T) {
|
|
| 439 |
+ testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
|
|
| 440 |
+ for indexName, expectedIndexInfo := range expectedIndexInfos {
|
|
| 441 |
+ index, err := config.NewIndexInfo(indexName) |
|
| 442 |
+ if err != nil {
|
|
| 443 |
+ t.Fatal(err) |
|
| 444 |
+ } else {
|
|
| 445 |
+ checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name") |
|
| 446 |
+ checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official") |
|
| 447 |
+ checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure") |
|
| 448 |
+ checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors") |
|
| 449 |
+ } |
|
| 450 |
+ } |
|
| 451 |
+ } |
|
| 452 |
+ |
|
| 453 |
+ config := NewServiceConfig(nil) |
|
| 454 |
+ noMirrors := make([]string, 0) |
|
| 455 |
+ expectedIndexInfos := map[string]*IndexInfo{
|
|
| 456 |
+ IndexServerName(): {
|
|
| 457 |
+ Name: IndexServerName(), |
|
| 458 |
+ Official: true, |
|
| 459 |
+ Secure: true, |
|
| 460 |
+ Mirrors: noMirrors, |
|
| 461 |
+ }, |
|
| 462 |
+ "index." + IndexServerName(): {
|
|
| 463 |
+ Name: IndexServerName(), |
|
| 464 |
+ Official: true, |
|
| 465 |
+ Secure: true, |
|
| 466 |
+ Mirrors: noMirrors, |
|
| 467 |
+ }, |
|
| 468 |
+ "example.com": {
|
|
| 469 |
+ Name: "example.com", |
|
| 470 |
+ Official: false, |
|
| 471 |
+ Secure: true, |
|
| 472 |
+ Mirrors: noMirrors, |
|
| 473 |
+ }, |
|
| 474 |
+ "127.0.0.1:5000": {
|
|
| 475 |
+ Name: "127.0.0.1:5000", |
|
| 476 |
+ Official: false, |
|
| 477 |
+ Secure: false, |
|
| 478 |
+ Mirrors: noMirrors, |
|
| 479 |
+ }, |
|
| 480 |
+ } |
|
| 481 |
+ testIndexInfo(config, expectedIndexInfos) |
|
| 482 |
+ |
|
| 483 |
+ publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"}
|
|
| 484 |
+ config = makeServiceConfig(publicMirrors, []string{"example.com"})
|
|
| 485 |
+ |
|
| 486 |
+ expectedIndexInfos = map[string]*IndexInfo{
|
|
| 487 |
+ IndexServerName(): {
|
|
| 488 |
+ Name: IndexServerName(), |
|
| 489 |
+ Official: true, |
|
| 490 |
+ Secure: true, |
|
| 491 |
+ Mirrors: publicMirrors, |
|
| 492 |
+ }, |
|
| 493 |
+ "index." + IndexServerName(): {
|
|
| 494 |
+ Name: IndexServerName(), |
|
| 495 |
+ Official: true, |
|
| 496 |
+ Secure: true, |
|
| 497 |
+ Mirrors: publicMirrors, |
|
| 498 |
+ }, |
|
| 499 |
+ "example.com": {
|
|
| 500 |
+ Name: "example.com", |
|
| 501 |
+ Official: false, |
|
| 502 |
+ Secure: false, |
|
| 503 |
+ Mirrors: noMirrors, |
|
| 504 |
+ }, |
|
| 505 |
+ "example.com:5000": {
|
|
| 506 |
+ Name: "example.com:5000", |
|
| 507 |
+ Official: false, |
|
| 508 |
+ Secure: true, |
|
| 509 |
+ Mirrors: noMirrors, |
|
| 510 |
+ }, |
|
| 511 |
+ "127.0.0.1": {
|
|
| 512 |
+ Name: "127.0.0.1", |
|
| 513 |
+ Official: false, |
|
| 514 |
+ Secure: false, |
|
| 515 |
+ Mirrors: noMirrors, |
|
| 516 |
+ }, |
|
| 517 |
+ "127.0.0.1:5000": {
|
|
| 518 |
+ Name: "127.0.0.1:5000", |
|
| 519 |
+ Official: false, |
|
| 520 |
+ Secure: false, |
|
| 521 |
+ Mirrors: noMirrors, |
|
| 522 |
+ }, |
|
| 523 |
+ "other.com": {
|
|
| 524 |
+ Name: "other.com", |
|
| 525 |
+ Official: false, |
|
| 526 |
+ Secure: true, |
|
| 527 |
+ Mirrors: noMirrors, |
|
| 528 |
+ }, |
|
| 529 |
+ } |
|
| 530 |
+ testIndexInfo(config, expectedIndexInfos) |
|
| 531 |
+ |
|
| 532 |
+ config = makeServiceConfig(nil, []string{"42.42.0.0/16"})
|
|
| 533 |
+ expectedIndexInfos = map[string]*IndexInfo{
|
|
| 534 |
+ "example.com": {
|
|
| 535 |
+ Name: "example.com", |
|
| 536 |
+ Official: false, |
|
| 537 |
+ Secure: false, |
|
| 538 |
+ Mirrors: noMirrors, |
|
| 539 |
+ }, |
|
| 540 |
+ "example.com:5000": {
|
|
| 541 |
+ Name: "example.com:5000", |
|
| 542 |
+ Official: false, |
|
| 543 |
+ Secure: false, |
|
| 544 |
+ Mirrors: noMirrors, |
|
| 545 |
+ }, |
|
| 546 |
+ "127.0.0.1": {
|
|
| 547 |
+ Name: "127.0.0.1", |
|
| 548 |
+ Official: false, |
|
| 549 |
+ Secure: false, |
|
| 550 |
+ Mirrors: noMirrors, |
|
| 551 |
+ }, |
|
| 552 |
+ "127.0.0.1:5000": {
|
|
| 553 |
+ Name: "127.0.0.1:5000", |
|
| 554 |
+ Official: false, |
|
| 555 |
+ Secure: false, |
|
| 556 |
+ Mirrors: noMirrors, |
|
| 557 |
+ }, |
|
| 558 |
+ "other.com": {
|
|
| 559 |
+ Name: "other.com", |
|
| 560 |
+ Official: false, |
|
| 561 |
+ Secure: true, |
|
| 562 |
+ Mirrors: noMirrors, |
|
| 563 |
+ }, |
|
| 180 | 564 |
} |
| 181 |
- assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be "+IndexServerAddress()) |
|
| 182 |
- assertEqual(t, repo, "ubuntu-12.04-base", "Expected endpoint to be ubuntu-12.04-base") |
|
| 565 |
+ testIndexInfo(config, expectedIndexInfos) |
|
| 183 | 566 |
} |
| 184 | 567 |
|
| 185 | 568 |
func TestPushRegistryTag(t *testing.T) {
|
| ... | ... |
@@ -232,7 +738,7 @@ func TestSearchRepositories(t *testing.T) {
|
| 232 | 232 |
assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars") |
| 233 | 233 |
} |
| 234 | 234 |
|
| 235 |
-func TestValidRepositoryName(t *testing.T) {
|
|
| 235 |
+func TestValidRemoteName(t *testing.T) {
|
|
| 236 | 236 |
validRepositoryNames := []string{
|
| 237 | 237 |
// Sanity check. |
| 238 | 238 |
"docker/docker", |
| ... | ... |
@@ -247,7 +753,7 @@ func TestValidRepositoryName(t *testing.T) {
|
| 247 | 247 |
"____/____", |
| 248 | 248 |
} |
| 249 | 249 |
for _, repositoryName := range validRepositoryNames {
|
| 250 |
- if err := validateRepositoryName(repositoryName); err != nil {
|
|
| 250 |
+ if err := validateRemoteName(repositoryName); err != nil {
|
|
| 251 | 251 |
t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
|
| 252 | 252 |
} |
| 253 | 253 |
} |
| ... | ... |
@@ -277,7 +783,7 @@ func TestValidRepositoryName(t *testing.T) {
|
| 277 | 277 |
"docker/", |
| 278 | 278 |
} |
| 279 | 279 |
for _, repositoryName := range invalidRepositoryNames {
|
| 280 |
- if err := validateRepositoryName(repositoryName); err == nil {
|
|
| 280 |
+ if err := validateRemoteName(repositoryName); err == nil {
|
|
| 281 | 281 |
t.Errorf("Repository name should be invalid: %v", repositoryName)
|
| 282 | 282 |
} |
| 283 | 283 |
} |
| ... | ... |
@@ -350,13 +856,13 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
|
| 350 | 350 |
} |
| 351 | 351 |
} |
| 352 | 352 |
|
| 353 |
-func TestIsSecure(t *testing.T) {
|
|
| 353 |
+func TestIsSecureIndex(t *testing.T) {
|
|
| 354 | 354 |
tests := []struct {
|
| 355 | 355 |
addr string |
| 356 | 356 |
insecureRegistries []string |
| 357 | 357 |
expected bool |
| 358 | 358 |
}{
|
| 359 |
- {IndexServerURL.Host, nil, true},
|
|
| 359 |
+ {IndexServerName(), nil, true},
|
|
| 360 | 360 |
{"example.com", []string{}, true},
|
| 361 | 361 |
{"example.com", []string{"example.com"}, false},
|
| 362 | 362 |
{"localhost", []string{"localhost:5000"}, false},
|
| ... | ... |
@@ -383,10 +889,9 @@ func TestIsSecure(t *testing.T) {
|
| 383 | 383 |
{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false},
|
| 384 | 384 |
} |
| 385 | 385 |
for _, tt := range tests {
|
| 386 |
- // TODO: remove this once we remove localhost insecure by default |
|
| 387 |
- insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8") |
|
| 388 |
- if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected {
|
|
| 389 |
- t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
|
|
| 386 |
+ config := makeServiceConfig(nil, tt.insecureRegistries) |
|
| 387 |
+ if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
|
|
| 388 |
+ t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
|
|
| 390 | 389 |
} |
| 391 | 390 |
} |
| 392 | 391 |
} |
| ... | ... |
@@ -13,14 +13,14 @@ import ( |
| 13 | 13 |
// 'pull': Download images from any registry (TODO) |
| 14 | 14 |
// 'push': Upload images to any registry (TODO) |
| 15 | 15 |
type Service struct {
|
| 16 |
- insecureRegistries []string |
|
| 16 |
+ Config *ServiceConfig |
|
| 17 | 17 |
} |
| 18 | 18 |
|
| 19 | 19 |
// NewService returns a new instance of Service ready to be |
| 20 | 20 |
// installed no an engine. |
| 21 |
-func NewService(insecureRegistries []string) *Service {
|
|
| 21 |
+func NewService(options *Options) *Service {
|
|
| 22 | 22 |
return &Service{
|
| 23 |
- insecureRegistries: insecureRegistries, |
|
| 23 |
+ Config: NewServiceConfig(options), |
|
| 24 | 24 |
} |
| 25 | 25 |
} |
| 26 | 26 |
|
| ... | ... |
@@ -28,6 +28,9 @@ func NewService(insecureRegistries []string) *Service {
|
| 28 | 28 |
func (s *Service) Install(eng *engine.Engine) error {
|
| 29 | 29 |
eng.Register("auth", s.Auth)
|
| 30 | 30 |
eng.Register("search", s.Search)
|
| 31 |
+ eng.Register("resolve_repository", s.ResolveRepository)
|
|
| 32 |
+ eng.Register("resolve_index", s.ResolveIndex)
|
|
| 33 |
+ eng.Register("registry_config", s.GetRegistryConfig)
|
|
| 31 | 34 |
return nil |
| 32 | 35 |
} |
| 33 | 36 |
|
| ... | ... |
@@ -39,15 +42,18 @@ func (s *Service) Auth(job *engine.Job) engine.Status {
|
| 39 | 39 |
|
| 40 | 40 |
job.GetenvJson("authConfig", authConfig)
|
| 41 | 41 |
|
| 42 |
- if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() {
|
|
| 43 |
- endpoint, err := NewEndpoint(addr, s.insecureRegistries) |
|
| 42 |
+ if authConfig.ServerAddress != "" {
|
|
| 43 |
+ index, err := ResolveIndexInfo(job, authConfig.ServerAddress) |
|
| 44 | 44 |
if err != nil {
|
| 45 | 45 |
return job.Error(err) |
| 46 | 46 |
} |
| 47 |
- if _, err := endpoint.Ping(); err != nil {
|
|
| 48 |
- return job.Error(err) |
|
| 47 |
+ if !index.Official {
|
|
| 48 |
+ endpoint, err := NewEndpoint(index) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ return job.Error(err) |
|
| 51 |
+ } |
|
| 52 |
+ authConfig.ServerAddress = endpoint.String() |
|
| 49 | 53 |
} |
| 50 |
- authConfig.ServerAddress = endpoint.String() |
|
| 51 | 54 |
} |
| 52 | 55 |
|
| 53 | 56 |
status, err := Login(authConfig, HTTPRequestFactory(nil)) |
| ... | ... |
@@ -87,12 +93,12 @@ func (s *Service) Search(job *engine.Job) engine.Status {
|
| 87 | 87 |
job.GetenvJson("authConfig", authConfig)
|
| 88 | 88 |
job.GetenvJson("metaHeaders", metaHeaders)
|
| 89 | 89 |
|
| 90 |
- hostname, term, err := ResolveRepositoryName(term) |
|
| 90 |
+ repoInfo, err := ResolveRepositoryInfo(job, term) |
|
| 91 | 91 |
if err != nil {
|
| 92 | 92 |
return job.Error(err) |
| 93 | 93 |
} |
| 94 |
- |
|
| 95 |
- endpoint, err := NewEndpoint(hostname, s.insecureRegistries) |
|
| 94 |
+ // *TODO: Search multiple indexes. |
|
| 95 |
+ endpoint, err := repoInfo.GetEndpoint() |
|
| 96 | 96 |
if err != nil {
|
| 97 | 97 |
return job.Error(err) |
| 98 | 98 |
} |
| ... | ... |
@@ -100,7 +106,7 @@ func (s *Service) Search(job *engine.Job) engine.Status {
|
| 100 | 100 |
if err != nil {
|
| 101 | 101 |
return job.Error(err) |
| 102 | 102 |
} |
| 103 |
- results, err := r.SearchRepositories(term) |
|
| 103 |
+ results, err := r.SearchRepositories(repoInfo.GetSearchTerm()) |
|
| 104 | 104 |
if err != nil {
|
| 105 | 105 |
return job.Error(err) |
| 106 | 106 |
} |
| ... | ... |
@@ -116,3 +122,92 @@ func (s *Service) Search(job *engine.Job) engine.Status {
|
| 116 | 116 |
} |
| 117 | 117 |
return engine.StatusOK |
| 118 | 118 |
} |
| 119 |
+ |
|
| 120 |
+// ResolveRepository splits a repository name into its components |
|
| 121 |
+// and configuration of the associated registry. |
|
| 122 |
+func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
|
|
| 123 |
+ var ( |
|
| 124 |
+ reposName = job.Args[0] |
|
| 125 |
+ ) |
|
| 126 |
+ |
|
| 127 |
+ repoInfo, err := s.Config.NewRepositoryInfo(reposName) |
|
| 128 |
+ if err != nil {
|
|
| 129 |
+ return job.Error(err) |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ out := engine.Env{}
|
|
| 133 |
+ err = out.SetJson("repository", repoInfo)
|
|
| 134 |
+ if err != nil {
|
|
| 135 |
+ return job.Error(err) |
|
| 136 |
+ } |
|
| 137 |
+ out.WriteTo(job.Stdout) |
|
| 138 |
+ |
|
| 139 |
+ return engine.StatusOK |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+// Convenience wrapper for calling resolve_repository Job from a running job. |
|
| 143 |
+func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*RepositoryInfo, error) {
|
|
| 144 |
+ job := jobContext.Eng.Job("resolve_repository", reposName)
|
|
| 145 |
+ env, err := job.Stdout.AddEnv() |
|
| 146 |
+ if err != nil {
|
|
| 147 |
+ return nil, err |
|
| 148 |
+ } |
|
| 149 |
+ if err := job.Run(); err != nil {
|
|
| 150 |
+ return nil, err |
|
| 151 |
+ } |
|
| 152 |
+ info := RepositoryInfo{}
|
|
| 153 |
+ if err := env.GetJson("repository", &info); err != nil {
|
|
| 154 |
+ return nil, err |
|
| 155 |
+ } |
|
| 156 |
+ return &info, nil |
|
| 157 |
+} |
|
| 158 |
+ |
|
| 159 |
+// ResolveIndex takes indexName and returns index info |
|
| 160 |
+func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
|
|
| 161 |
+ var ( |
|
| 162 |
+ indexName = job.Args[0] |
|
| 163 |
+ ) |
|
| 164 |
+ |
|
| 165 |
+ index, err := s.Config.NewIndexInfo(indexName) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ return job.Error(err) |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ out := engine.Env{}
|
|
| 171 |
+ err = out.SetJson("index", index)
|
|
| 172 |
+ if err != nil {
|
|
| 173 |
+ return job.Error(err) |
|
| 174 |
+ } |
|
| 175 |
+ out.WriteTo(job.Stdout) |
|
| 176 |
+ |
|
| 177 |
+ return engine.StatusOK |
|
| 178 |
+} |
|
| 179 |
+ |
|
| 180 |
+// Convenience wrapper for calling resolve_index Job from a running job. |
|
| 181 |
+func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, error) {
|
|
| 182 |
+ job := jobContext.Eng.Job("resolve_index", indexName)
|
|
| 183 |
+ env, err := job.Stdout.AddEnv() |
|
| 184 |
+ if err != nil {
|
|
| 185 |
+ return nil, err |
|
| 186 |
+ } |
|
| 187 |
+ if err := job.Run(); err != nil {
|
|
| 188 |
+ return nil, err |
|
| 189 |
+ } |
|
| 190 |
+ info := IndexInfo{}
|
|
| 191 |
+ if err := env.GetJson("index", &info); err != nil {
|
|
| 192 |
+ return nil, err |
|
| 193 |
+ } |
|
| 194 |
+ return &info, nil |
|
| 195 |
+} |
|
| 196 |
+ |
|
| 197 |
+// GetRegistryConfig returns current registry configuration. |
|
| 198 |
+func (s *Service) GetRegistryConfig(job *engine.Job) engine.Status {
|
|
| 199 |
+ out := engine.Env{}
|
|
| 200 |
+ err := out.SetJson("config", s.Config)
|
|
| 201 |
+ if err != nil {
|
|
| 202 |
+ return job.Error(err) |
|
| 203 |
+ } |
|
| 204 |
+ out.WriteTo(job.Stdout) |
|
| 205 |
+ |
|
| 206 |
+ return engine.StatusOK |
|
| 207 |
+} |
| ... | ... |
@@ -65,3 +65,44 @@ const ( |
| 65 | 65 |
APIVersion1 = iota + 1 |
| 66 | 66 |
APIVersion2 |
| 67 | 67 |
) |
| 68 |
+ |
|
| 69 |
+// RepositoryInfo Examples: |
|
| 70 |
+// {
|
|
| 71 |
+// "Index" : {
|
|
| 72 |
+// "Name" : "docker.io", |
|
| 73 |
+// "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"], |
|
| 74 |
+// "Secure" : true, |
|
| 75 |
+// "Official" : true, |
|
| 76 |
+// }, |
|
| 77 |
+// "RemoteName" : "library/debian", |
|
| 78 |
+// "LocalName" : "debian", |
|
| 79 |
+// "CanonicalName" : "docker.io/debian" |
|
| 80 |
+// "Official" : true, |
|
| 81 |
+// } |
|
| 82 |
+ |
|
| 83 |
+// {
|
|
| 84 |
+// "Index" : {
|
|
| 85 |
+// "Name" : "127.0.0.1:5000", |
|
| 86 |
+// "Mirrors" : [], |
|
| 87 |
+// "Secure" : false, |
|
| 88 |
+// "Official" : false, |
|
| 89 |
+// }, |
|
| 90 |
+// "RemoteName" : "user/repo", |
|
| 91 |
+// "LocalName" : "127.0.0.1:5000/user/repo", |
|
| 92 |
+// "CanonicalName" : "127.0.0.1:5000/user/repo", |
|
| 93 |
+// "Official" : false, |
|
| 94 |
+// } |
|
| 95 |
+type IndexInfo struct {
|
|
| 96 |
+ Name string |
|
| 97 |
+ Mirrors []string |
|
| 98 |
+ Secure bool |
|
| 99 |
+ Official bool |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+type RepositoryInfo struct {
|
|
| 103 |
+ Index *IndexInfo |
|
| 104 |
+ RemoteName string |
|
| 105 |
+ LocalName string |
|
| 106 |
+ CanonicalName string |
|
| 107 |
+ Official bool |
|
| 108 |
+} |
| ... | ... |
@@ -134,6 +134,10 @@ func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) {
|
| 134 | 134 |
self.decorators = append(self.decorators, d...) |
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 |
+func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator {
|
|
| 138 |
+ return self.decorators |
|
| 139 |
+} |
|
| 140 |
+ |
|
| 137 | 141 |
// NewRequest() creates a new *http.Request, |
| 138 | 142 |
// applies all decorators in the HTTPRequestFactory on the request, |
| 139 | 143 |
// then applies decorators provided by d on the request. |