Browse code

Deprecating ResolveRepositoryName

Passing RepositoryInfo to ResolveAuthConfig, pullRepository, and pushRepository

Moving --registry-mirror configuration to registry config

Created resolve_repository job

Repo names with 'index.docker.io' or 'docker.io' are now synonymous with omitting an index name.

Adding test for RepositoryInfo

Adding tests for opts.StringSetOpts and registry.ValidateMirror

Fixing search term use of repoInfo

Adding integration tests for registry mirror configuration

Normalizing LookupImage image name to match LocalName parsing rules

Normalizing repository LocalName to avoid multiple references to an official image

Removing errorOut use in tests

Removing TODO comment

gofmt changes

golint comments cleanup. renaming RegistryOptions => registry.Options, and RegistryServiceConfig => registry.ServiceConfig

Splitting out builtins.Registry and registry.NewService calls

Stray whitespace cleanup

Moving integration tests for Mirrors and InsecureRegistries into TestNewIndexInfo unit test

Factoring out ValidateRepositoryName from NewRepositoryInfo

Removing unused IndexServerURL

Allowing json marshaling of ServiceConfig. Exposing ServiceConfig in /info

Switching to CamelCase for json marshaling

PR cleanup; removing 'Is' prefix from boolean members. Removing unneeded json tags.

Removing non-cleanup related fix for 'localhost:[port]' in splitReposName

Merge fixes for gh9735

Fixing integration test

Reapplying #9754

Adding comment on config.IndexConfigs use from isSecureIndex

Remove unused error return value from isSecureIndex

Signed-off-by: Don Kjer <don.kjer@gmail.com>

Adding back comment in isSecureIndex

Signed-off-by: Don Kjer <don.kjer@gmail.com>

Don Kjer authored on 2014/10/07 10:54:52
Showing 31 changed files
... ...
@@ -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 {
... ...
@@ -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 {
... ...
@@ -841,7 +841,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
841 841
 	}
842 842
 
843 843
 	log.Debugf("Creating repository list")
844
-	repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors, config.InsecureRegistries)
844
+	repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
845 845
 	if err != nil {
846 846
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
847 847
 	}
... ...
@@ -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", &registryConfig); 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 = &registry.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  = &registry.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"
... ...
@@ -22,23 +21,15 @@ const (
22 22
 	// Only used for user auth + account creation
23 23
 	INDEXSERVER    = "https://index.docker.io/v1/"
24 24
 	REGISTRYSERVER = "https://registry-1.docker.io/v1/"
25
+	INDEXNAME      = "docker.io"
25 26
 
26 27
 	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
27 28
 )
28 29
 
29 30
 var (
30 31
 	ErrConfigFileMissing = errors.New("The Auth config file is missing")
31
-	IndexServerURL       *url.URL
32 32
 )
33 33
 
34
-func init() {
35
-	url, err := url.Parse(INDEXSERVER)
36
-	if err != nil {
37
-		panic(err)
38
-	}
39
-	IndexServerURL = url
40
-}
41
-
42 34
 type AuthConfig struct {
43 35
 	Username      string `json:"username,omitempty"`
44 36
 	Password      string `json:"password,omitempty"`
... ...
@@ -56,6 +47,10 @@ func IndexServerAddress() string {
56 56
 	return INDEXSERVER
57 57
 }
58 58
 
59
+func IndexServerName() string {
60
+	return INDEXNAME
61
+}
62
+
59 63
 // create a base64 encoded auth string to store in config
60 64
 func encodeAuth(authConfig *AuthConfig) string {
61 65
 	authStr := authConfig.Username + ":" + authConfig.Password
... ...
@@ -118,6 +113,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 +177,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 +209,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 +233,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 +269,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 +291,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,126 @@
0
+package registry
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"net"
6
+	"net/url"
7
+
8
+	"github.com/docker/docker/opts"
9
+	flag "github.com/docker/docker/pkg/mflag"
10
+)
11
+
12
+// Options holds command line options.
13
+type Options struct {
14
+	Mirrors            opts.ListOpts
15
+	InsecureRegistries opts.ListOpts
16
+}
17
+
18
+// InstallFlags adds command-line options to the top-level flag parser for
19
+// the current process.
20
+func (options *Options) InstallFlags() {
21
+	options.Mirrors = opts.NewListOpts(ValidateMirror)
22
+	flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
23
+	options.InsecureRegistries = opts.NewListOpts(ValidateIndexName)
24
+	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)")
25
+}
26
+
27
+// ValidateMirror validates an HTTP(S) registry mirror
28
+func ValidateMirror(val string) (string, error) {
29
+	uri, err := url.Parse(val)
30
+	if err != nil {
31
+		return "", fmt.Errorf("%s is not a valid URI", val)
32
+	}
33
+
34
+	if uri.Scheme != "http" && uri.Scheme != "https" {
35
+		return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
36
+	}
37
+
38
+	if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
39
+		return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
40
+	}
41
+
42
+	return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
43
+}
44
+
45
+// ValidateIndexName validates an index name.
46
+func ValidateIndexName(val string) (string, error) {
47
+	// 'index.docker.io' => 'docker.io'
48
+	if val == "index."+IndexServerName() {
49
+		val = IndexServerName()
50
+	}
51
+	// *TODO: Check if valid hostname[:port]/ip[:port]?
52
+	return val, nil
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
+}
0 126
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
... ...
@@ -156,27 +158,30 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
156 156
 	return info, nil
157 157
 }
158 158
 
159
-// isSecure returns false if the provided hostname is part of the list of insecure registries.
159
+// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
160 160
 // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
161 161
 //
162 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
163
+// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
164 164
 // insecure.
165 165
 //
166
-// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
166
+// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
167 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
168
+// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
169 169
 // of insecureRegistries.
170
-func isSecure(hostname string, insecureRegistries []string) (bool, error) {
171
-	if hostname == IndexServerURL.Host {
172
-		return true, nil
170
+func (config *ServiceConfig) isSecureIndex(indexName string) bool {
171
+	// Check for configured index, first.  This is needed in case isSecureIndex
172
+	// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
173
+	if index, ok := config.IndexConfigs[indexName]; ok {
174
+		return index.Secure
173 175
 	}
174 176
 
175
-	host, _, err := net.SplitHostPort(hostname)
177
+	host, _, err := net.SplitHostPort(indexName)
176 178
 	if err != nil {
177
-		// assume hostname is of the form `host` without the port and go on.
178
-		host = hostname
179
+		// assume indexName is of the form `host` without the port and go on.
180
+		host = indexName
179 181
 	}
182
+
180 183
 	addrs, err := lookupIP(host)
181 184
 	if err != nil {
182 185
 		ip := net.ParseIP(host)
... ...
@@ -189,29 +194,15 @@ func isSecure(hostname string, insecureRegistries []string) (bool, error) {
189 189
 		// So, len(addrs) == 0 and we're not aborting.
190 190
 	}
191 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
-
192
+	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
193
+	for _, addr := range addrs {
194
+		for _, ipnet := range config.InsecureRegistryCIDRs {
209 195
 			// check if the addr falls in the subnet
210
-			if ipnet.Contains(addr) {
211
-				return false, nil
196
+			if (*net.IPNet)(ipnet).Contains(addr) {
197
+				return false
212 198
 			}
213 199
 		}
214 200
 	}
215 201
 
216
-	return true, nil
202
+	return true
217 203
 }
... ...
@@ -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
 		}
... ...
@@ -25,6 +25,7 @@ var (
25 25
 	errLoginRequired         = errors.New("Authentication is required.")
26 26
 	validNamespaceChars      = regexp.MustCompile(`^([a-z0-9-_]*)$`)
27 27
 	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
28
+	emptyServiceConfig       = NewServiceConfig(nil)
28 29
 )
29 30
 
30 31
 type TimeoutType uint32
... ...
@@ -160,12 +161,12 @@ 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 {
163
+func validateRemoteName(remoteName string) error {
164 164
 	var (
165 165
 		namespace string
166 166
 		name      string
167 167
 	)
168
-	nameParts := strings.SplitN(repositoryName, "/", 2)
168
+	nameParts := strings.SplitN(remoteName, "/", 2)
169 169
 	if len(nameParts) < 2 {
170 170
 		namespace = "library"
171 171
 		name = nameParts[0]
... ...
@@ -196,29 +197,147 @@ func validateRepositoryName(repositoryName string) error {
196 196
 	return nil
197 197
 }
198 198
 
199
-// Resolves a repository name to a hostname + name
200
-func ResolveRepositoryName(reposName string) (string, string, error) {
199
+// NewIndexInfo returns IndexInfo configuration from indexName
200
+func NewIndexInfo(config *ServiceConfig, indexName string) (*IndexInfo, error) {
201
+	var err error
202
+	indexName, err = ValidateIndexName(indexName)
203
+	if err != nil {
204
+		return nil, err
205
+	}
206
+
207
+	// Return any configured index info, first.
208
+	if index, ok := config.IndexConfigs[indexName]; ok {
209
+		return index, nil
210
+	}
211
+
212
+	// Construct a non-configured index info.
213
+	index := &IndexInfo{
214
+		Name:     indexName,
215
+		Mirrors:  make([]string, 0),
216
+		Official: false,
217
+	}
218
+	index.Secure = config.isSecureIndex(indexName)
219
+	return index, nil
220
+}
221
+
222
+func validateNoSchema(reposName string) error {
201 223
 	if strings.Contains(reposName, "://") {
202 224
 		// It cannot contain a scheme!
203
-		return "", "", ErrInvalidRepositoryName
225
+		return ErrInvalidRepositoryName
204 226
 	}
227
+	return nil
228
+}
229
+
230
+// splitReposName breaks a reposName into an index name and remote name
231
+func splitReposName(reposName string) (string, string) {
205 232
 	nameParts := strings.SplitN(reposName, "/", 2)
206
-	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") &&
207
-		nameParts[0] != "localhost") {
233
+	var indexName, remoteName string
234
+	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
235
+		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
208 236
 		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
209
-		err := validateRepositoryName(reposName)
210
-		return IndexServerAddress(), reposName, err
237
+		// 'docker.io'
238
+		indexName = IndexServerName()
239
+		remoteName = reposName
240
+	} else {
241
+		indexName = nameParts[0]
242
+		remoteName = nameParts[1]
243
+	}
244
+	return indexName, remoteName
245
+}
246
+
247
+// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
248
+func NewRepositoryInfo(config *ServiceConfig, reposName string) (*RepositoryInfo, error) {
249
+	if err := validateNoSchema(reposName); err != nil {
250
+		return nil, err
251
+	}
252
+
253
+	indexName, remoteName := splitReposName(reposName)
254
+	if err := validateRemoteName(remoteName); err != nil {
255
+		return nil, err
211 256
 	}
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)
257
+
258
+	repoInfo := &RepositoryInfo{
259
+		RemoteName: remoteName,
216 260
 	}
217
-	if err := validateRepositoryName(reposName); err != nil {
218
-		return "", "", err
261
+
262
+	var err error
263
+	repoInfo.Index, err = NewIndexInfo(config, indexName)
264
+	if err != nil {
265
+		return nil, err
219 266
 	}
220 267
 
221
-	return hostname, reposName, nil
268
+	if repoInfo.Index.Official {
269
+		normalizedName := repoInfo.RemoteName
270
+		if strings.HasPrefix(normalizedName, "library/") {
271
+			// If pull "library/foo", it's stored locally under "foo"
272
+			normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
273
+		}
274
+
275
+		repoInfo.LocalName = normalizedName
276
+		repoInfo.RemoteName = normalizedName
277
+		// If the normalized name does not contain a '/' (e.g. "foo")
278
+		// then it is an official repo.
279
+		if strings.IndexRune(normalizedName, '/') == -1 {
280
+			repoInfo.Official = true
281
+			// Fix up remote name for official repos.
282
+			repoInfo.RemoteName = "library/" + normalizedName
283
+		}
284
+
285
+		// *TODO: Prefix this with 'docker.io/'.
286
+		repoInfo.CanonicalName = repoInfo.LocalName
287
+	} else {
288
+		// *TODO: Decouple index name from hostname (via registry configuration?)
289
+		repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
290
+		repoInfo.CanonicalName = repoInfo.LocalName
291
+	}
292
+	return repoInfo, nil
293
+}
294
+
295
+// ValidateRepositoryName validates a repository name
296
+func ValidateRepositoryName(reposName string) error {
297
+	var err error
298
+	if err = validateNoSchema(reposName); err != nil {
299
+		return err
300
+	}
301
+	indexName, remoteName := splitReposName(reposName)
302
+	if _, err = ValidateIndexName(indexName); err != nil {
303
+		return err
304
+	}
305
+	return validateRemoteName(remoteName)
306
+}
307
+
308
+// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
309
+// lacks registry configuration.
310
+func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
311
+	return NewRepositoryInfo(emptyServiceConfig, reposName)
312
+}
313
+
314
+// NormalizeLocalName transforms a repository name into a normalize LocalName
315
+// Passes through the name without transformation on error (image id, etc)
316
+func NormalizeLocalName(name string) string {
317
+	repoInfo, err := ParseRepositoryInfo(name)
318
+	if err != nil {
319
+		return name
320
+	}
321
+	return repoInfo.LocalName
322
+}
323
+
324
+// GetAuthConfigKey special-cases using the full index address of the official
325
+// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
326
+func (index *IndexInfo) GetAuthConfigKey() string {
327
+	if index.Official {
328
+		return IndexServerAddress()
329
+	}
330
+	return index.Name
331
+}
332
+
333
+// GetSearchTerm special-cases using local name for official index, and
334
+// remote name for private indexes.
335
+func (repoInfo *RepositoryInfo) GetSearchTerm() string {
336
+	if repoInfo.Index.Official {
337
+		return repoInfo.LocalName
338
+	}
339
+	return repoInfo.RemoteName
222 340
 }
223 341
 
224 342
 func trustedLocation(req *http.Request) bool {
... ...
@@ -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 := NewIndexInfo(config, 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 := NewRepositoryInfo(s.Config, 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 := NewIndexInfo(s.Config, 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.