Browse code

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

Cleanup: Replace ResolveRepositoryName with RepositoryInfo{}

Tibor Vass authored on 2015/01/09 07:19:03
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 {
... ...
@@ -260,7 +260,10 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
260 260
 	sigchan := make(chan os.Signal, 1)
261 261
 	gosignal.Notify(sigchan, signal.SIGWINCH)
262 262
 	go func() {
263
-		for _ = range sigchan {
263
+		// This tmp := range..., _ = tmp workaround is needed to
264
+		// suppress gofmt warnings while still preserve go1.3 compatibility
265
+		for tmp := range sigchan {
266
+			_ = tmp
264 267
 			cli.resizeTty(id, isExec)
265 268
 		}
266 269
 	}()
... ...
@@ -427,17 +427,17 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) {
427 427
 	if tag == "" {
428 428
 		tag = "latest"
429 429
 	}
430
+	job := b.Engine.Job("pull", remote, tag)
430 431
 	pullRegistryAuth := b.AuthConfig
431 432
 	if len(b.AuthConfigFile.Configs) > 0 {
432 433
 		// The request came with a full auth config file, we prefer to use that
433
-		endpoint, _, err := registry.ResolveRepositoryName(remote)
434
+		repoInfo, err := registry.ResolveRepositoryInfo(job, remote)
434 435
 		if err != nil {
435 436
 			return nil, err
436 437
 		}
437
-		resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(endpoint)
438
+		resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index)
438 439
 		pullRegistryAuth = &resolvedAuth
439 440
 	}
440
-	job := b.Engine.Job("pull", remote, tag)
441 441
 	job.SetenvBool("json", b.StreamFormatter.Json())
442 442
 	job.SetenvBool("parallel", true)
443 443
 	job.SetenvJson("authConfig", pullRegistryAuth)
... ...
@@ -50,7 +50,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
50 50
 
51 51
 	repoName, tag = parsers.ParseRepositoryTag(repoName)
52 52
 	if repoName != "" {
53
-		if _, _, err := registry.ResolveRepositoryName(repoName); err != nil {
53
+		if err := registry.ValidateRepositoryName(repoName); err != nil {
54 54
 			return job.Error(err)
55 55
 		}
56 56
 		if len(tag) > 0 {
... ...
@@ -23,7 +23,6 @@ type Config struct {
23 23
 	AutoRestart                 bool
24 24
 	Dns                         []string
25 25
 	DnsSearch                   []string
26
-	Mirrors                     []string
27 26
 	EnableIptables              bool
28 27
 	EnableIpForward             bool
29 28
 	EnableIpMasq                bool
... ...
@@ -31,7 +30,6 @@ type Config struct {
31 31
 	BridgeIface                 string
32 32
 	BridgeIP                    string
33 33
 	FixedCIDR                   string
34
-	InsecureRegistries          []string
35 34
 	InterContainerCommunication bool
36 35
 	GraphDriver                 string
37 36
 	GraphOptions                []string
... ...
@@ -58,7 +56,6 @@ func (config *Config) InstallFlags() {
58 58
 	flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b")
59 59
 	flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
60 60
 	flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)\nthis subnet must be nested in the bridge subnet (which is defined by -b or --bip)")
61
-	opts.ListVar(&config.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
62 61
 	flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Allow unrestricted inter-container and Docker daemon host communication")
63 62
 	flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Force the Docker runtime to use a specific storage driver")
64 63
 	flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Force the Docker runtime to use a specific exec driver")
... ...
@@ -69,16 +66,7 @@ func (config *Config) InstallFlags() {
69 69
 	// FIXME: why the inconsistency between "hosts" and "sockets"?
70 70
 	opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "Force Docker to use specific DNS servers")
71 71
 	opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
72
-	opts.MirrorListVar(&config.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
73 72
 	opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon (displayed in `docker info`)")
74
-
75
-	// Localhost is by default considered as an insecure registry
76
-	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
77
-	//
78
-	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
79
-	// daemon flags on boot2docker?
80
-	// If so, do not forget to check the TODO in TestIsSecure
81
-	config.InsecureRegistries = append(config.InsecureRegistries, "127.0.0.0/8")
82 73
 }
83 74
 
84 75
 func getDefaultNetworkMtu() int {
... ...
@@ -898,7 +898,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
898 898
 	}
899 899
 
900 900
 	log.Debugf("Creating repository list")
901
-	repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, config.Mirrors, config.InsecureRegistries)
901
+	repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g)
902 902
 	if err != nil {
903 903
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
904 904
 	}
... ...
@@ -55,6 +55,15 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status {
55 55
 	if err := cjob.Run(); err != nil {
56 56
 		return job.Error(err)
57 57
 	}
58
+	registryJob := job.Eng.Job("registry_config")
59
+	registryEnv, _ := registryJob.Stdout.AddEnv()
60
+	if err := registryJob.Run(); err != nil {
61
+		return job.Error(err)
62
+	}
63
+	registryConfig := registry.ServiceConfig{}
64
+	if err := registryEnv.GetJson("config", &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"
... ...
@@ -18,27 +17,12 @@ import (
18 18
 const (
19 19
 	// Where we store the config file
20 20
 	CONFIGFILE = ".dockercfg"
21
-
22
-	// Only used for user auth + account creation
23
-	INDEXSERVER    = "https://index.docker.io/v1/"
24
-	REGISTRYSERVER = "https://registry-1.docker.io/v1/"
25
-
26
-	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
27 21
 )
28 22
 
29 23
 var (
30 24
 	ErrConfigFileMissing = errors.New("The Auth config file is missing")
31
-	IndexServerURL       *url.URL
32 25
 )
33 26
 
34
-func init() {
35
-	url, err := url.Parse(INDEXSERVER)
36
-	if err != nil {
37
-		panic(err)
38
-	}
39
-	IndexServerURL = url
40
-}
41
-
42 27
 type AuthConfig struct {
43 28
 	Username      string `json:"username,omitempty"`
44 29
 	Password      string `json:"password,omitempty"`
... ...
@@ -52,10 +36,6 @@ type ConfigFile struct {
52 52
 	rootPath string
53 53
 }
54 54
 
55
-func IndexServerAddress() string {
56
-	return INDEXSERVER
57
-}
58
-
59 55
 // create a base64 encoded auth string to store in config
60 56
 func encodeAuth(authConfig *AuthConfig) string {
61 57
 	authStr := authConfig.Username + ":" + authConfig.Password
... ...
@@ -118,6 +98,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) {
118 118
 		}
119 119
 		authConfig.Email = origEmail[1]
120 120
 		authConfig.ServerAddress = IndexServerAddress()
121
+		// *TODO: Switch to using IndexServerName() instead?
121 122
 		configFile.Configs[IndexServerAddress()] = authConfig
122 123
 	} else {
123 124
 		for k, authConfig := range configFile.Configs {
... ...
@@ -181,7 +162,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
181 181
 	)
182 182
 
183 183
 	if serverAddress == "" {
184
-		serverAddress = IndexServerAddress()
184
+		return "", fmt.Errorf("Server Error: Server Address not set.")
185 185
 	}
186 186
 
187 187
 	loginAgainstOfficialIndex := serverAddress == IndexServerAddress()
... ...
@@ -213,6 +194,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
213 213
 			status = "Account created. Please use the confirmation link we sent" +
214 214
 				" to your e-mail to activate it."
215 215
 		} else {
216
+			// *TODO: Use registry configuration to determine what this says, if anything?
216 217
 			status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
217 218
 		}
218 219
 	} else if reqStatusCode == 400 {
... ...
@@ -236,6 +218,7 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
236 236
 				if loginAgainstOfficialIndex {
237 237
 					return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
238 238
 				}
239
+				// *TODO: Use registry configuration to determine what this says, if anything?
239 240
 				return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
240 241
 			}
241 242
 			return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
... ...
@@ -271,14 +254,10 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e
271 271
 }
272 272
 
273 273
 // this method matches a auth configuration to a server address or a url
274
-func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
275
-	if hostname == IndexServerAddress() || len(hostname) == 0 {
276
-		// default to the index server
277
-		return config.Configs[IndexServerAddress()]
278
-	}
279
-
274
+func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig {
275
+	configKey := index.GetAuthConfigKey()
280 276
 	// First try the happy case
281
-	if c, found := config.Configs[hostname]; found {
277
+	if c, found := config.Configs[configKey]; found || index.Official {
282 278
 		return c
283 279
 	}
284 280
 
... ...
@@ -297,9 +276,8 @@ func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig {
297 297
 
298 298
 	// Maybe they have a legacy config file, we will iterate the keys converting
299 299
 	// them to the new format and testing
300
-	normalizedHostename := convertToHostname(hostname)
301 300
 	for registry, config := range config.Configs {
302
-		if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename {
301
+		if configKey == convertToHostname(registry) {
303 302
 			return config
304 303
 		}
305 304
 	}
... ...
@@ -81,12 +81,20 @@ func TestResolveAuthConfigIndexServer(t *testing.T) {
81 81
 	}
82 82
 	defer os.RemoveAll(configFile.rootPath)
83 83
 
84
-	for _, registry := range []string{"", IndexServerAddress()} {
85
-		resolved := configFile.ResolveAuthConfig(registry)
86
-		if resolved != configFile.Configs[IndexServerAddress()] {
87
-			t.Fail()
88
-		}
84
+	indexConfig := configFile.Configs[IndexServerAddress()]
85
+
86
+	officialIndex := &IndexInfo{
87
+		Official: true,
88
+	}
89
+	privateIndex := &IndexInfo{
90
+		Official: false,
89 91
 	}
92
+
93
+	resolved := configFile.ResolveAuthConfig(officialIndex)
94
+	assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()")
95
+
96
+	resolved = configFile.ResolveAuthConfig(privateIndex)
97
+	assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()")
90 98
 }
91 99
 
92 100
 func TestResolveAuthConfigFullURL(t *testing.T) {
... ...
@@ -106,18 +114,27 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
106 106
 		Password: "bar-pass",
107 107
 		Email:    "bar@example.com",
108 108
 	}
109
-	configFile.Configs["https://registry.example.com/v1/"] = registryAuth
110
-	configFile.Configs["http://localhost:8000/v1/"] = localAuth
111
-	configFile.Configs["registry.com"] = registryAuth
109
+	officialAuth := AuthConfig{
110
+		Username: "baz-user",
111
+		Password: "baz-pass",
112
+		Email:    "baz@example.com",
113
+	}
114
+	configFile.Configs[IndexServerAddress()] = officialAuth
115
+
116
+	expectedAuths := map[string]AuthConfig{
117
+		"registry.example.com": registryAuth,
118
+		"localhost:8000":       localAuth,
119
+		"registry.com":         localAuth,
120
+	}
112 121
 
113 122
 	validRegistries := map[string][]string{
114
-		"https://registry.example.com/v1/": {
123
+		"registry.example.com": {
115 124
 			"https://registry.example.com/v1/",
116 125
 			"http://registry.example.com/v1/",
117 126
 			"registry.example.com",
118 127
 			"registry.example.com/v1/",
119 128
 		},
120
-		"http://localhost:8000/v1/": {
129
+		"localhost:8000": {
121 130
 			"https://localhost:8000/v1/",
122 131
 			"http://localhost:8000/v1/",
123 132
 			"localhost:8000",
... ...
@@ -132,18 +149,24 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
132 132
 	}
133 133
 
134 134
 	for configKey, registries := range validRegistries {
135
+		configured, ok := expectedAuths[configKey]
136
+		if !ok || configured.Email == "" {
137
+			t.Fatal()
138
+		}
139
+		index := &IndexInfo{
140
+			Name: configKey,
141
+		}
135 142
 		for _, registry := range registries {
136
-			var (
137
-				configured AuthConfig
138
-				ok         bool
139
-			)
140
-			resolved := configFile.ResolveAuthConfig(registry)
141
-			if configured, ok = configFile.Configs[configKey]; !ok {
142
-				t.Fail()
143
-			}
143
+			configFile.Configs[registry] = configured
144
+			resolved := configFile.ResolveAuthConfig(index)
144 145
 			if resolved.Email != configured.Email {
145 146
 				t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
146 147
 			}
148
+			delete(configFile.Configs, registry)
149
+			resolved = configFile.ResolveAuthConfig(index)
150
+			if resolved.Email == configured.Email {
151
+				t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
152
+			}
147 153
 		}
148 154
 	}
149 155
 }
150 156
new file mode 100644
... ...
@@ -0,0 +1,382 @@
0
+package registry
1
+
2
+import (
3
+	"encoding/json"
4
+	"errors"
5
+	"fmt"
6
+	"net"
7
+	"net/url"
8
+	"regexp"
9
+	"strings"
10
+
11
+	"github.com/docker/docker/opts"
12
+	flag "github.com/docker/docker/pkg/mflag"
13
+	"github.com/docker/docker/utils"
14
+)
15
+
16
+// Options holds command line options.
17
+type Options struct {
18
+	Mirrors            opts.ListOpts
19
+	InsecureRegistries opts.ListOpts
20
+}
21
+
22
+const (
23
+	// Only used for user auth + account creation
24
+	INDEXSERVER    = "https://index.docker.io/v1/"
25
+	REGISTRYSERVER = "https://registry-1.docker.io/v1/"
26
+	INDEXNAME      = "docker.io"
27
+
28
+	// INDEXSERVER = "https://registry-stage.hub.docker.com/v1/"
29
+)
30
+
31
+var (
32
+	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
33
+	emptyServiceConfig       = NewServiceConfig(nil)
34
+	validNamespaceChars      = regexp.MustCompile(`^([a-z0-9-_]*)$`)
35
+	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
36
+)
37
+
38
+func IndexServerAddress() string {
39
+	return INDEXSERVER
40
+}
41
+
42
+func IndexServerName() string {
43
+	return INDEXNAME
44
+}
45
+
46
+// InstallFlags adds command-line options to the top-level flag parser for
47
+// the current process.
48
+func (options *Options) InstallFlags() {
49
+	options.Mirrors = opts.NewListOpts(ValidateMirror)
50
+	flag.Var(&options.Mirrors, []string{"-registry-mirror"}, "Specify a preferred Docker registry mirror")
51
+	options.InsecureRegistries = opts.NewListOpts(ValidateIndexName)
52
+	flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure communication with specified registries (no certificate verification for HTTPS and enable HTTP fallback) (e.g., localhost:5000 or 10.20.0.0/16)")
53
+}
54
+
55
+type netIPNet net.IPNet
56
+
57
+func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
58
+	return json.Marshal((*net.IPNet)(ipnet).String())
59
+}
60
+
61
+func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
62
+	var ipnet_str string
63
+	if err = json.Unmarshal(b, &ipnet_str); err == nil {
64
+		var cidr *net.IPNet
65
+		if _, cidr, err = net.ParseCIDR(ipnet_str); err == nil {
66
+			*ipnet = netIPNet(*cidr)
67
+		}
68
+	}
69
+	return
70
+}
71
+
72
+// ServiceConfig stores daemon registry services configuration.
73
+type ServiceConfig struct {
74
+	InsecureRegistryCIDRs []*netIPNet           `json:"InsecureRegistryCIDRs"`
75
+	IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
76
+}
77
+
78
+// NewServiceConfig returns a new instance of ServiceConfig
79
+func NewServiceConfig(options *Options) *ServiceConfig {
80
+	if options == nil {
81
+		options = &Options{
82
+			Mirrors:            opts.NewListOpts(nil),
83
+			InsecureRegistries: opts.NewListOpts(nil),
84
+		}
85
+	}
86
+
87
+	// Localhost is by default considered as an insecure registry
88
+	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
89
+	//
90
+	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
91
+	// daemon flags on boot2docker?
92
+	options.InsecureRegistries.Set("127.0.0.0/8")
93
+
94
+	config := &ServiceConfig{
95
+		InsecureRegistryCIDRs: make([]*netIPNet, 0),
96
+		IndexConfigs:          make(map[string]*IndexInfo, 0),
97
+	}
98
+	// Split --insecure-registry into CIDR and registry-specific settings.
99
+	for _, r := range options.InsecureRegistries.GetAll() {
100
+		// Check if CIDR was passed to --insecure-registry
101
+		_, ipnet, err := net.ParseCIDR(r)
102
+		if err == nil {
103
+			// Valid CIDR.
104
+			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*netIPNet)(ipnet))
105
+		} else {
106
+			// Assume `host:port` if not CIDR.
107
+			config.IndexConfigs[r] = &IndexInfo{
108
+				Name:     r,
109
+				Mirrors:  make([]string, 0),
110
+				Secure:   false,
111
+				Official: false,
112
+			}
113
+		}
114
+	}
115
+
116
+	// Configure public registry.
117
+	config.IndexConfigs[IndexServerName()] = &IndexInfo{
118
+		Name:     IndexServerName(),
119
+		Mirrors:  options.Mirrors.GetAll(),
120
+		Secure:   true,
121
+		Official: true,
122
+	}
123
+
124
+	return config
125
+}
126
+
127
+// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
128
+// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
129
+//
130
+// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
131
+// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
132
+// insecure.
133
+//
134
+// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
135
+// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
136
+// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
137
+// of insecureRegistries.
138
+func (config *ServiceConfig) isSecureIndex(indexName string) bool {
139
+	// Check for configured index, first.  This is needed in case isSecureIndex
140
+	// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
141
+	if index, ok := config.IndexConfigs[indexName]; ok {
142
+		return index.Secure
143
+	}
144
+
145
+	host, _, err := net.SplitHostPort(indexName)
146
+	if err != nil {
147
+		// assume indexName is of the form `host` without the port and go on.
148
+		host = indexName
149
+	}
150
+
151
+	addrs, err := lookupIP(host)
152
+	if err != nil {
153
+		ip := net.ParseIP(host)
154
+		if ip != nil {
155
+			addrs = []net.IP{ip}
156
+		}
157
+
158
+		// if ip == nil, then `host` is neither an IP nor it could be looked up,
159
+		// either because the index is unreachable, or because the index is behind an HTTP proxy.
160
+		// So, len(addrs) == 0 and we're not aborting.
161
+	}
162
+
163
+	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
164
+	for _, addr := range addrs {
165
+		for _, ipnet := range config.InsecureRegistryCIDRs {
166
+			// check if the addr falls in the subnet
167
+			if (*net.IPNet)(ipnet).Contains(addr) {
168
+				return false
169
+			}
170
+		}
171
+	}
172
+
173
+	return true
174
+}
175
+
176
+// ValidateMirror validates an HTTP(S) registry mirror
177
+func ValidateMirror(val string) (string, error) {
178
+	uri, err := url.Parse(val)
179
+	if err != nil {
180
+		return "", fmt.Errorf("%s is not a valid URI", val)
181
+	}
182
+
183
+	if uri.Scheme != "http" && uri.Scheme != "https" {
184
+		return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
185
+	}
186
+
187
+	if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
188
+		return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
189
+	}
190
+
191
+	return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
192
+}
193
+
194
+// ValidateIndexName validates an index name.
195
+func ValidateIndexName(val string) (string, error) {
196
+	// 'index.docker.io' => 'docker.io'
197
+	if val == "index."+IndexServerName() {
198
+		val = IndexServerName()
199
+	}
200
+	// *TODO: Check if valid hostname[:port]/ip[:port]?
201
+	return val, nil
202
+}
203
+
204
+func validateRemoteName(remoteName string) error {
205
+	var (
206
+		namespace string
207
+		name      string
208
+	)
209
+	nameParts := strings.SplitN(remoteName, "/", 2)
210
+	if len(nameParts) < 2 {
211
+		namespace = "library"
212
+		name = nameParts[0]
213
+
214
+		// the repository name must not be a valid image ID
215
+		if err := utils.ValidateID(name); err == nil {
216
+			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
217
+		}
218
+	} else {
219
+		namespace = nameParts[0]
220
+		name = nameParts[1]
221
+	}
222
+	if !validNamespaceChars.MatchString(namespace) {
223
+		return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
224
+	}
225
+	if len(namespace) < 4 || len(namespace) > 30 {
226
+		return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
227
+	}
228
+	if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
229
+		return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
230
+	}
231
+	if strings.Contains(namespace, "--") {
232
+		return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
233
+	}
234
+	if !validRepo.MatchString(name) {
235
+		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
236
+	}
237
+	return nil
238
+}
239
+
240
+func validateNoSchema(reposName string) error {
241
+	if strings.Contains(reposName, "://") {
242
+		// It cannot contain a scheme!
243
+		return ErrInvalidRepositoryName
244
+	}
245
+	return nil
246
+}
247
+
248
+// ValidateRepositoryName validates a repository name
249
+func ValidateRepositoryName(reposName string) error {
250
+	var err error
251
+	if err = validateNoSchema(reposName); err != nil {
252
+		return err
253
+	}
254
+	indexName, remoteName := splitReposName(reposName)
255
+	if _, err = ValidateIndexName(indexName); err != nil {
256
+		return err
257
+	}
258
+	return validateRemoteName(remoteName)
259
+}
260
+
261
+// NewIndexInfo returns IndexInfo configuration from indexName
262
+func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
263
+	var err error
264
+	indexName, err = ValidateIndexName(indexName)
265
+	if err != nil {
266
+		return nil, err
267
+	}
268
+
269
+	// Return any configured index info, first.
270
+	if index, ok := config.IndexConfigs[indexName]; ok {
271
+		return index, nil
272
+	}
273
+
274
+	// Construct a non-configured index info.
275
+	index := &IndexInfo{
276
+		Name:     indexName,
277
+		Mirrors:  make([]string, 0),
278
+		Official: false,
279
+	}
280
+	index.Secure = config.isSecureIndex(indexName)
281
+	return index, nil
282
+}
283
+
284
+// GetAuthConfigKey special-cases using the full index address of the official
285
+// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
286
+func (index *IndexInfo) GetAuthConfigKey() string {
287
+	if index.Official {
288
+		return IndexServerAddress()
289
+	}
290
+	return index.Name
291
+}
292
+
293
+// splitReposName breaks a reposName into an index name and remote name
294
+func splitReposName(reposName string) (string, string) {
295
+	nameParts := strings.SplitN(reposName, "/", 2)
296
+	var indexName, remoteName string
297
+	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
298
+		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
299
+		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
300
+		// 'docker.io'
301
+		indexName = IndexServerName()
302
+		remoteName = reposName
303
+	} else {
304
+		indexName = nameParts[0]
305
+		remoteName = nameParts[1]
306
+	}
307
+	return indexName, remoteName
308
+}
309
+
310
+// NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
311
+func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInfo, error) {
312
+	if err := validateNoSchema(reposName); err != nil {
313
+		return nil, err
314
+	}
315
+
316
+	indexName, remoteName := splitReposName(reposName)
317
+	if err := validateRemoteName(remoteName); err != nil {
318
+		return nil, err
319
+	}
320
+
321
+	repoInfo := &RepositoryInfo{
322
+		RemoteName: remoteName,
323
+	}
324
+
325
+	var err error
326
+	repoInfo.Index, err = config.NewIndexInfo(indexName)
327
+	if err != nil {
328
+		return nil, err
329
+	}
330
+
331
+	if repoInfo.Index.Official {
332
+		normalizedName := repoInfo.RemoteName
333
+		if strings.HasPrefix(normalizedName, "library/") {
334
+			// If pull "library/foo", it's stored locally under "foo"
335
+			normalizedName = strings.SplitN(normalizedName, "/", 2)[1]
336
+		}
337
+
338
+		repoInfo.LocalName = normalizedName
339
+		repoInfo.RemoteName = normalizedName
340
+		// If the normalized name does not contain a '/' (e.g. "foo")
341
+		// then it is an official repo.
342
+		if strings.IndexRune(normalizedName, '/') == -1 {
343
+			repoInfo.Official = true
344
+			// Fix up remote name for official repos.
345
+			repoInfo.RemoteName = "library/" + normalizedName
346
+		}
347
+
348
+		// *TODO: Prefix this with 'docker.io/'.
349
+		repoInfo.CanonicalName = repoInfo.LocalName
350
+	} else {
351
+		// *TODO: Decouple index name from hostname (via registry configuration?)
352
+		repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
353
+		repoInfo.CanonicalName = repoInfo.LocalName
354
+	}
355
+	return repoInfo, nil
356
+}
357
+
358
+// GetSearchTerm special-cases using local name for official index, and
359
+// remote name for private indexes.
360
+func (repoInfo *RepositoryInfo) GetSearchTerm() string {
361
+	if repoInfo.Index.Official {
362
+		return repoInfo.LocalName
363
+	}
364
+	return repoInfo.RemoteName
365
+}
366
+
367
+// ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
368
+// lacks registry configuration.
369
+func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
370
+	return emptyServiceConfig.NewRepositoryInfo(reposName)
371
+}
372
+
373
+// NormalizeLocalName transforms a repository name into a normalize LocalName
374
+// Passes through the name without transformation on error (image id, etc)
375
+func NormalizeLocalName(name string) string {
376
+	repoInfo, err := ParseRepositoryInfo(name)
377
+	if err != nil {
378
+		return name
379
+	}
380
+	return repoInfo.LocalName
381
+}
0 382
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package registry
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestValidateMirror(t *testing.T) {
7
+	valid := []string{
8
+		"http://mirror-1.com",
9
+		"https://mirror-1.com",
10
+		"http://localhost",
11
+		"https://localhost",
12
+		"http://localhost:5000",
13
+		"https://localhost:5000",
14
+		"http://127.0.0.1",
15
+		"https://127.0.0.1",
16
+		"http://127.0.0.1:5000",
17
+		"https://127.0.0.1:5000",
18
+	}
19
+
20
+	invalid := []string{
21
+		"!invalid!://%as%",
22
+		"ftp://mirror-1.com",
23
+		"http://mirror-1.com/",
24
+		"http://mirror-1.com/?q=foo",
25
+		"http://mirror-1.com/v1/",
26
+		"http://mirror-1.com/v1/?q=foo",
27
+		"http://mirror-1.com/v1/?q=foo#frag",
28
+		"http://mirror-1.com?q=foo",
29
+		"https://mirror-1.com#frag",
30
+		"https://mirror-1.com/",
31
+		"https://mirror-1.com/#frag",
32
+		"https://mirror-1.com/v1/",
33
+		"https://mirror-1.com/v1/#",
34
+		"https://mirror-1.com?q",
35
+	}
36
+
37
+	for _, address := range valid {
38
+		if ret, err := ValidateMirror(address); err != nil || ret == "" {
39
+			t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
40
+		}
41
+	}
42
+
43
+	for _, address := range invalid {
44
+		if ret, err := ValidateMirror(address); err == nil || ret != "" {
45
+			t.Errorf("ValidateMirror(`"+address+"`) got %s %s", ret, err)
46
+		}
47
+	}
48
+}
... ...
@@ -37,8 +37,9 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
37 37
 	return hostname, DefaultAPIVersion
38 38
 }
39 39
 
40
-func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
41
-	endpoint, err := newEndpoint(hostname, insecureRegistries)
40
+func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
41
+	// *TODO: Allow per-registry configuration of endpoints.
42
+	endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
42 43
 	if err != nil {
43 44
 		return nil, err
44 45
 	}
... ...
@@ -49,7 +50,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
49 49
 
50 50
 		//TODO: triggering highland build can be done there without "failing"
51 51
 
52
-		if endpoint.secure {
52
+		if index.Secure {
53 53
 			// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
54 54
 			// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
55 55
 			return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host)
... ...
@@ -68,7 +69,7 @@ func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
68 68
 
69 69
 	return endpoint, nil
70 70
 }
71
-func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) {
71
+func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
72 72
 	var (
73 73
 		endpoint        = Endpoint{}
74 74
 		trimmedHostname string
... ...
@@ -82,13 +83,14 @@ func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error
82 82
 	if err != nil {
83 83
 		return nil, err
84 84
 	}
85
-	endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries)
86
-	if err != nil {
87
-		return nil, err
88
-	}
85
+	endpoint.secure = secure
89 86
 	return &endpoint, nil
90 87
 }
91 88
 
89
+func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
90
+	return NewEndpoint(repoInfo.Index)
91
+}
92
+
92 93
 type Endpoint struct {
93 94
 	URL     *url.URL
94 95
 	Version APIVersion
... ...
@@ -155,63 +157,3 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
155 155
 	log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
156 156
 	return info, nil
157 157
 }
158
-
159
-// isSecure returns false if the provided hostname is part of the list of insecure registries.
160
-// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
161
-//
162
-// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
163
-// If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered
164
-// insecure.
165
-//
166
-// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
167
-// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
168
-// in a subnet. If the resolving is not successful, isSecure will only try to match hostname to any element
169
-// of insecureRegistries.
170
-func isSecure(hostname string, insecureRegistries []string) (bool, error) {
171
-	if hostname == IndexServerURL.Host {
172
-		return true, nil
173
-	}
174
-
175
-	host, _, err := net.SplitHostPort(hostname)
176
-	if err != nil {
177
-		// assume hostname is of the form `host` without the port and go on.
178
-		host = hostname
179
-	}
180
-	addrs, err := lookupIP(host)
181
-	if err != nil {
182
-		ip := net.ParseIP(host)
183
-		if ip != nil {
184
-			addrs = []net.IP{ip}
185
-		}
186
-
187
-		// if ip == nil, then `host` is neither an IP nor it could be looked up,
188
-		// either because the index is unreachable, or because the index is behind an HTTP proxy.
189
-		// So, len(addrs) == 0 and we're not aborting.
190
-	}
191
-
192
-	for _, r := range insecureRegistries {
193
-		if hostname == r {
194
-			// hostname matches insecure registry
195
-			return false, nil
196
-		}
197
-
198
-		// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
199
-		for _, addr := range addrs {
200
-
201
-			// now assume a CIDR was passed to --insecure-registry
202
-			_, ipnet, err := net.ParseCIDR(r)
203
-			if err != nil {
204
-				// if we could not parse it as a CIDR, even after removing
205
-				// assume it's not a CIDR and go on with the next candidate
206
-				break
207
-			}
208
-
209
-			// check if the addr falls in the subnet
210
-			if ipnet.Contains(addr) {
211
-				return false, nil
212
-			}
213
-		}
214
-	}
215
-
216
-	return true, nil
217
-}
... ...
@@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
12 12
 		{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
13 13
 	}
14 14
 	for _, td := range testData {
15
-		e, err := newEndpoint(td.str, insecureRegistries)
15
+		e, err := newEndpoint(td.str, false)
16 16
 		if err != nil {
17 17
 			t.Errorf("%q: %s", td.str, err)
18 18
 		}
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"net/http"
11 11
 	"os"
12 12
 	"path"
13
-	"regexp"
14 13
 	"strings"
15 14
 	"time"
16 15
 
... ...
@@ -19,12 +18,9 @@ import (
19 19
 )
20 20
 
21 21
 var (
22
-	ErrAlreadyExists         = errors.New("Image already exists")
23
-	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
24
-	ErrDoesNotExist          = errors.New("Image does not exist")
25
-	errLoginRequired         = errors.New("Authentication is required.")
26
-	validNamespaceChars      = regexp.MustCompile(`^([a-z0-9-_]*)$`)
27
-	validRepo                = regexp.MustCompile(`^([a-z0-9-_.]+)$`)
22
+	ErrAlreadyExists = errors.New("Image already exists")
23
+	ErrDoesNotExist  = errors.New("Image does not exist")
24
+	errLoginRequired = errors.New("Authentication is required.")
28 25
 )
29 26
 
30 27
 type TimeoutType uint32
... ...
@@ -160,67 +156,6 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur
160 160
 	return res, client, err
161 161
 }
162 162
 
163
-func validateRepositoryName(repositoryName string) error {
164
-	var (
165
-		namespace string
166
-		name      string
167
-	)
168
-	nameParts := strings.SplitN(repositoryName, "/", 2)
169
-	if len(nameParts) < 2 {
170
-		namespace = "library"
171
-		name = nameParts[0]
172
-
173
-		// the repository name must not be a valid image ID
174
-		if err := utils.ValidateID(name); err == nil {
175
-			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name)
176
-		}
177
-	} else {
178
-		namespace = nameParts[0]
179
-		name = nameParts[1]
180
-	}
181
-	if !validNamespaceChars.MatchString(namespace) {
182
-		return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace)
183
-	}
184
-	if len(namespace) < 4 || len(namespace) > 30 {
185
-		return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 4 or more than 30 characters.", namespace)
186
-	}
187
-	if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") {
188
-		return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace)
189
-	}
190
-	if strings.Contains(namespace, "--") {
191
-		return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace)
192
-	}
193
-	if !validRepo.MatchString(name) {
194
-		return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
195
-	}
196
-	return nil
197
-}
198
-
199
-// Resolves a repository name to a hostname + name
200
-func ResolveRepositoryName(reposName string) (string, string, error) {
201
-	if strings.Contains(reposName, "://") {
202
-		// It cannot contain a scheme!
203
-		return "", "", ErrInvalidRepositoryName
204
-	}
205
-	nameParts := strings.SplitN(reposName, "/", 2)
206
-	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") &&
207
-		nameParts[0] != "localhost") {
208
-		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
209
-		err := validateRepositoryName(reposName)
210
-		return IndexServerAddress(), reposName, err
211
-	}
212
-	hostname := nameParts[0]
213
-	reposName = nameParts[1]
214
-	if strings.Contains(hostname, "index.docker.io") {
215
-		return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName)
216
-	}
217
-	if err := validateRepositoryName(reposName); err != nil {
218
-		return "", "", err
219
-	}
220
-
221
-	return hostname, reposName, nil
222
-}
223
-
224 163
 func trustedLocation(req *http.Request) bool {
225 164
 	var (
226 165
 		trusteds = []string{"docker.com", "docker.io"}
... ...
@@ -15,15 +15,16 @@ import (
15 15
 	"testing"
16 16
 	"time"
17 17
 
18
+	"github.com/docker/docker/opts"
18 19
 	"github.com/gorilla/mux"
19 20
 
20 21
 	log "github.com/Sirupsen/logrus"
21 22
 )
22 23
 
23 24
 var (
24
-	testHTTPServer     *httptest.Server
25
-	insecureRegistries []string
26
-	testLayers         = map[string]map[string]string{
25
+	testHTTPServer  *httptest.Server
26
+	testHTTPSServer *httptest.Server
27
+	testLayers      = map[string]map[string]string{
27 28
 		"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": {
28 29
 			"json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20",
29 30
 				"comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00",
... ...
@@ -86,6 +87,7 @@ var (
86 86
 		"":            {net.ParseIP("0.0.0.0")},
87 87
 		"localhost":   {net.ParseIP("127.0.0.1"), net.ParseIP("::1")},
88 88
 		"example.com": {net.ParseIP("42.42.42.42")},
89
+		"other.com":   {net.ParseIP("43.43.43.43")},
89 90
 	}
90 91
 )
91 92
 
... ...
@@ -108,11 +110,7 @@ func init() {
108 108
 	r.HandleFunc("/v2/version", handlerGetPing).Methods("GET")
109 109
 
110 110
 	testHTTPServer = httptest.NewServer(handlerAccessLog(r))
111
-	URL, err := url.Parse(testHTTPServer.URL)
112
-	if err != nil {
113
-		panic(err)
114
-	}
115
-	insecureRegistries = []string{URL.Host}
111
+	testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r))
116 112
 
117 113
 	// override net.LookupIP
118 114
 	lookupIP = func(host string) ([]net.IP, error) {
... ...
@@ -146,6 +144,52 @@ func makeURL(req string) string {
146 146
 	return testHTTPServer.URL + req
147 147
 }
148 148
 
149
+func makeHttpsURL(req string) string {
150
+	return testHTTPSServer.URL + req
151
+}
152
+
153
+func makeIndex(req string) *IndexInfo {
154
+	index := &IndexInfo{
155
+		Name: makeURL(req),
156
+	}
157
+	return index
158
+}
159
+
160
+func makeHttpsIndex(req string) *IndexInfo {
161
+	index := &IndexInfo{
162
+		Name: makeHttpsURL(req),
163
+	}
164
+	return index
165
+}
166
+
167
+func makePublicIndex() *IndexInfo {
168
+	index := &IndexInfo{
169
+		Name:     IndexServerAddress(),
170
+		Secure:   true,
171
+		Official: true,
172
+	}
173
+	return index
174
+}
175
+
176
+func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceConfig {
177
+	options := &Options{
178
+		Mirrors:            opts.NewListOpts(nil),
179
+		InsecureRegistries: opts.NewListOpts(nil),
180
+	}
181
+	if mirrors != nil {
182
+		for _, mirror := range mirrors {
183
+			options.Mirrors.Set(mirror)
184
+		}
185
+	}
186
+	if insecure_registries != nil {
187
+		for _, insecure_registries := range insecure_registries {
188
+			options.InsecureRegistries.Set(insecure_registries)
189
+		}
190
+	}
191
+
192
+	return NewServiceConfig(options)
193
+}
194
+
149 195
 func writeHeaders(w http.ResponseWriter) {
150 196
 	h := w.Header()
151 197
 	h.Add("Server", "docker-tests/mock")
... ...
@@ -193,6 +237,40 @@ func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
193 193
 	t.Fatal(message)
194 194
 }
195 195
 
196
+func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) {
197
+	if a != b {
198
+		return
199
+	}
200
+	if len(message) == 0 {
201
+		message = fmt.Sprintf("%v == %v", a, b)
202
+	}
203
+	t.Fatal(message)
204
+}
205
+
206
+// Similar to assertEqual, but does not stop test
207
+func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
208
+	if a == b {
209
+		return
210
+	}
211
+	message := fmt.Sprintf("%v != %v", a, b)
212
+	if len(messagePrefix) != 0 {
213
+		message = messagePrefix + ": " + message
214
+	}
215
+	t.Error(message)
216
+}
217
+
218
+// Similar to assertNotEqual, but does not stop test
219
+func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) {
220
+	if a != b {
221
+		return
222
+	}
223
+	message := fmt.Sprintf("%v == %v", a, b)
224
+	if len(messagePrefix) != 0 {
225
+		message = messagePrefix + ": " + message
226
+	}
227
+	t.Error(message)
228
+}
229
+
196 230
 func requiresAuth(w http.ResponseWriter, r *http.Request) bool {
197 231
 	writeCookie := func() {
198 232
 		value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())
... ...
@@ -271,6 +349,7 @@ func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) {
271 271
 		return
272 272
 	}
273 273
 	repositoryName := mux.Vars(r)["repository"]
274
+	repositoryName = NormalizeLocalName(repositoryName)
274 275
 	tags, exists := testRepositories[repositoryName]
275 276
 	if !exists {
276 277
 		apiError(w, "Repository not found", 404)
... ...
@@ -290,6 +369,7 @@ func handlerGetTag(w http.ResponseWriter, r *http.Request) {
290 290
 	}
291 291
 	vars := mux.Vars(r)
292 292
 	repositoryName := vars["repository"]
293
+	repositoryName = NormalizeLocalName(repositoryName)
293 294
 	tagName := vars["tag"]
294 295
 	tags, exists := testRepositories[repositoryName]
295 296
 	if !exists {
... ...
@@ -310,6 +390,7 @@ func handlerPutTag(w http.ResponseWriter, r *http.Request) {
310 310
 	}
311 311
 	vars := mux.Vars(r)
312 312
 	repositoryName := vars["repository"]
313
+	repositoryName = NormalizeLocalName(repositoryName)
313 314
 	tagName := vars["tag"]
314 315
 	tags, exists := testRepositories[repositoryName]
315 316
 	if !exists {
... ...
@@ -21,7 +21,7 @@ const (
21 21
 
22 22
 func spawnTestRegistrySession(t *testing.T) *Session {
23 23
 	authConfig := &AuthConfig{}
24
-	endpoint, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
24
+	endpoint, err := NewEndpoint(makeIndex("/v1/"))
25 25
 	if err != nil {
26 26
 		t.Fatal(err)
27 27
 	}
... ...
@@ -32,16 +32,139 @@ func spawnTestRegistrySession(t *testing.T) *Session {
32 32
 	return r
33 33
 }
34 34
 
35
+func TestPublicSession(t *testing.T) {
36
+	authConfig := &AuthConfig{}
37
+
38
+	getSessionDecorators := func(index *IndexInfo) int {
39
+		endpoint, err := NewEndpoint(index)
40
+		if err != nil {
41
+			t.Fatal(err)
42
+		}
43
+		r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
44
+		if err != nil {
45
+			t.Fatal(err)
46
+		}
47
+		return len(r.reqFactory.GetDecorators())
48
+	}
49
+
50
+	decorators := getSessionDecorators(makeIndex("/v1/"))
51
+	assertEqual(t, decorators, 0, "Expected no decorator on http session")
52
+
53
+	decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
54
+	assertNotEqual(t, decorators, 0, "Expected decorator on https session")
55
+
56
+	decorators = getSessionDecorators(makePublicIndex())
57
+	assertEqual(t, decorators, 0, "Expected no decorator on public session")
58
+}
59
+
35 60
 func TestPingRegistryEndpoint(t *testing.T) {
36
-	ep, err := NewEndpoint(makeURL("/v1/"), insecureRegistries)
37
-	if err != nil {
38
-		t.Fatal(err)
61
+	testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
62
+		ep, err := NewEndpoint(index)
63
+		if err != nil {
64
+			t.Fatal(err)
65
+		}
66
+		regInfo, err := ep.Ping()
67
+		if err != nil {
68
+			t.Fatal(err)
69
+		}
70
+
71
+		assertEqual(t, regInfo.Standalone, expectedStandalone, assertMessage)
39 72
 	}
40
-	regInfo, err := ep.Ping()
41
-	if err != nil {
42
-		t.Fatal(err)
73
+
74
+	testPing(makeIndex("/v1/"), true, "Expected standalone to be true (default)")
75
+	testPing(makeHttpsIndex("/v1/"), true, "Expected standalone to be true (default)")
76
+	testPing(makePublicIndex(), false, "Expected standalone to be false for public index")
77
+}
78
+
79
+func TestEndpoint(t *testing.T) {
80
+	// Simple wrapper to fail test if err != nil
81
+	expandEndpoint := func(index *IndexInfo) *Endpoint {
82
+		endpoint, err := NewEndpoint(index)
83
+		if err != nil {
84
+			t.Fatal(err)
85
+		}
86
+		return endpoint
87
+	}
88
+
89
+	assertInsecureIndex := func(index *IndexInfo) {
90
+		index.Secure = true
91
+		_, err := NewEndpoint(index)
92
+		assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
93
+		assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry  error for insecure index")
94
+		index.Secure = false
95
+	}
96
+
97
+	assertSecureIndex := func(index *IndexInfo) {
98
+		index.Secure = true
99
+		_, err := NewEndpoint(index)
100
+		assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
101
+		assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
102
+		index.Secure = false
103
+	}
104
+
105
+	index := &IndexInfo{}
106
+	index.Name = makeURL("/v1/")
107
+	endpoint := expandEndpoint(index)
108
+	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
109
+	if endpoint.Version != APIVersion1 {
110
+		t.Fatal("Expected endpoint to be v1")
111
+	}
112
+	assertInsecureIndex(index)
113
+
114
+	index.Name = makeURL("")
115
+	endpoint = expandEndpoint(index)
116
+	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
117
+	if endpoint.Version != APIVersion1 {
118
+		t.Fatal("Expected endpoint to be v1")
119
+	}
120
+	assertInsecureIndex(index)
121
+
122
+	httpURL := makeURL("")
123
+	index.Name = strings.SplitN(httpURL, "://", 2)[1]
124
+	endpoint = expandEndpoint(index)
125
+	assertEqual(t, endpoint.String(), httpURL+"/v1/", index.Name+": Expected endpoint to be "+httpURL+"/v1/")
126
+	if endpoint.Version != APIVersion1 {
127
+		t.Fatal("Expected endpoint to be v1")
128
+	}
129
+	assertInsecureIndex(index)
130
+
131
+	index.Name = makeHttpsURL("/v1/")
132
+	endpoint = expandEndpoint(index)
133
+	assertEqual(t, endpoint.String(), index.Name, "Expected endpoint to be "+index.Name)
134
+	if endpoint.Version != APIVersion1 {
135
+		t.Fatal("Expected endpoint to be v1")
136
+	}
137
+	assertSecureIndex(index)
138
+
139
+	index.Name = makeHttpsURL("")
140
+	endpoint = expandEndpoint(index)
141
+	assertEqual(t, endpoint.String(), index.Name+"/v1/", index.Name+": Expected endpoint to be "+index.Name+"/v1/")
142
+	if endpoint.Version != APIVersion1 {
143
+		t.Fatal("Expected endpoint to be v1")
144
+	}
145
+	assertSecureIndex(index)
146
+
147
+	httpsURL := makeHttpsURL("")
148
+	index.Name = strings.SplitN(httpsURL, "://", 2)[1]
149
+	endpoint = expandEndpoint(index)
150
+	assertEqual(t, endpoint.String(), httpsURL+"/v1/", index.Name+": Expected endpoint to be "+httpsURL+"/v1/")
151
+	if endpoint.Version != APIVersion1 {
152
+		t.Fatal("Expected endpoint to be v1")
153
+	}
154
+	assertSecureIndex(index)
155
+
156
+	badEndpoints := []string{
157
+		"http://127.0.0.1/v1/",
158
+		"https://127.0.0.1/v1/",
159
+		"http://127.0.0.1",
160
+		"https://127.0.0.1",
161
+		"127.0.0.1",
162
+	}
163
+	for _, address := range badEndpoints {
164
+		index.Name = address
165
+		_, err := NewEndpoint(index)
166
+		checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
43 167
 	}
44
-	assertEqual(t, regInfo.Standalone, true, "Expected standalone to be true (default)")
45 168
 }
46 169
 
47 170
 func TestGetRemoteHistory(t *testing.T) {
... ...
@@ -156,30 +279,413 @@ func TestPushImageLayerRegistry(t *testing.T) {
156 156
 	}
157 157
 }
158 158
 
159
-func TestResolveRepositoryName(t *testing.T) {
160
-	_, _, err := ResolveRepositoryName("https://github.com/docker/docker")
161
-	assertEqual(t, err, ErrInvalidRepositoryName, "Expected error invalid repo name")
162
-	ep, repo, err := ResolveRepositoryName("fooo/bar")
163
-	if err != nil {
164
-		t.Fatal(err)
159
+func TestValidateRepositoryName(t *testing.T) {
160
+	validRepoNames := []string{
161
+		"docker/docker",
162
+		"library/debian",
163
+		"debian",
164
+		"docker.io/docker/docker",
165
+		"docker.io/library/debian",
166
+		"docker.io/debian",
167
+		"index.docker.io/docker/docker",
168
+		"index.docker.io/library/debian",
169
+		"index.docker.io/debian",
170
+		"127.0.0.1:5000/docker/docker",
171
+		"127.0.0.1:5000/library/debian",
172
+		"127.0.0.1:5000/debian",
173
+		"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
174
+	}
175
+	invalidRepoNames := []string{
176
+		"https://github.com/docker/docker",
177
+		"docker/Docker",
178
+		"docker///docker",
179
+		"docker.io/docker/Docker",
180
+		"docker.io/docker///docker",
181
+		"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
182
+		"docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
165 183
 	}
166
-	assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address")
167
-	assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar")
168 184
 
169
-	u := makeURL("")[7:]
170
-	ep, repo, err = ResolveRepositoryName(u + "/private/moonbase")
171
-	if err != nil {
172
-		t.Fatal(err)
185
+	for _, name := range invalidRepoNames {
186
+		err := ValidateRepositoryName(name)
187
+		assertNotEqual(t, err, nil, "Expected invalid repo name: "+name)
173 188
 	}
174
-	assertEqual(t, ep, u, "Expected endpoint to be "+u)
175
-	assertEqual(t, repo, "private/moonbase", "Expected endpoint to be private/moonbase")
176 189
 
177
-	ep, repo, err = ResolveRepositoryName("ubuntu-12.04-base")
178
-	if err != nil {
179
-		t.Fatal(err)
190
+	for _, name := range validRepoNames {
191
+		err := ValidateRepositoryName(name)
192
+		assertEqual(t, err, nil, "Expected valid repo name: "+name)
193
+	}
194
+
195
+	err := ValidateRepositoryName(invalidRepoNames[0])
196
+	assertEqual(t, err, ErrInvalidRepositoryName, "Expected ErrInvalidRepositoryName: "+invalidRepoNames[0])
197
+}
198
+
199
+func TestParseRepositoryInfo(t *testing.T) {
200
+	expectedRepoInfos := map[string]RepositoryInfo{
201
+		"fooo/bar": {
202
+			Index: &IndexInfo{
203
+				Name:     IndexServerName(),
204
+				Official: true,
205
+			},
206
+			RemoteName:    "fooo/bar",
207
+			LocalName:     "fooo/bar",
208
+			CanonicalName: "fooo/bar",
209
+			Official:      false,
210
+		},
211
+		"library/ubuntu": {
212
+			Index: &IndexInfo{
213
+				Name:     IndexServerName(),
214
+				Official: true,
215
+			},
216
+			RemoteName:    "library/ubuntu",
217
+			LocalName:     "ubuntu",
218
+			CanonicalName: "ubuntu",
219
+			Official:      true,
220
+		},
221
+		"nonlibrary/ubuntu": {
222
+			Index: &IndexInfo{
223
+				Name:     IndexServerName(),
224
+				Official: true,
225
+			},
226
+			RemoteName:    "nonlibrary/ubuntu",
227
+			LocalName:     "nonlibrary/ubuntu",
228
+			CanonicalName: "nonlibrary/ubuntu",
229
+			Official:      false,
230
+		},
231
+		"ubuntu": {
232
+			Index: &IndexInfo{
233
+				Name:     IndexServerName(),
234
+				Official: true,
235
+			},
236
+			RemoteName:    "library/ubuntu",
237
+			LocalName:     "ubuntu",
238
+			CanonicalName: "ubuntu",
239
+			Official:      true,
240
+		},
241
+		"other/library": {
242
+			Index: &IndexInfo{
243
+				Name:     IndexServerName(),
244
+				Official: true,
245
+			},
246
+			RemoteName:    "other/library",
247
+			LocalName:     "other/library",
248
+			CanonicalName: "other/library",
249
+			Official:      false,
250
+		},
251
+		"127.0.0.1:8000/private/moonbase": {
252
+			Index: &IndexInfo{
253
+				Name:     "127.0.0.1:8000",
254
+				Official: false,
255
+			},
256
+			RemoteName:    "private/moonbase",
257
+			LocalName:     "127.0.0.1:8000/private/moonbase",
258
+			CanonicalName: "127.0.0.1:8000/private/moonbase",
259
+			Official:      false,
260
+		},
261
+		"127.0.0.1:8000/privatebase": {
262
+			Index: &IndexInfo{
263
+				Name:     "127.0.0.1:8000",
264
+				Official: false,
265
+			},
266
+			RemoteName:    "privatebase",
267
+			LocalName:     "127.0.0.1:8000/privatebase",
268
+			CanonicalName: "127.0.0.1:8000/privatebase",
269
+			Official:      false,
270
+		},
271
+		"localhost:8000/private/moonbase": {
272
+			Index: &IndexInfo{
273
+				Name:     "localhost:8000",
274
+				Official: false,
275
+			},
276
+			RemoteName:    "private/moonbase",
277
+			LocalName:     "localhost:8000/private/moonbase",
278
+			CanonicalName: "localhost:8000/private/moonbase",
279
+			Official:      false,
280
+		},
281
+		"localhost:8000/privatebase": {
282
+			Index: &IndexInfo{
283
+				Name:     "localhost:8000",
284
+				Official: false,
285
+			},
286
+			RemoteName:    "privatebase",
287
+			LocalName:     "localhost:8000/privatebase",
288
+			CanonicalName: "localhost:8000/privatebase",
289
+			Official:      false,
290
+		},
291
+		"example.com/private/moonbase": {
292
+			Index: &IndexInfo{
293
+				Name:     "example.com",
294
+				Official: false,
295
+			},
296
+			RemoteName:    "private/moonbase",
297
+			LocalName:     "example.com/private/moonbase",
298
+			CanonicalName: "example.com/private/moonbase",
299
+			Official:      false,
300
+		},
301
+		"example.com/privatebase": {
302
+			Index: &IndexInfo{
303
+				Name:     "example.com",
304
+				Official: false,
305
+			},
306
+			RemoteName:    "privatebase",
307
+			LocalName:     "example.com/privatebase",
308
+			CanonicalName: "example.com/privatebase",
309
+			Official:      false,
310
+		},
311
+		"example.com:8000/private/moonbase": {
312
+			Index: &IndexInfo{
313
+				Name:     "example.com:8000",
314
+				Official: false,
315
+			},
316
+			RemoteName:    "private/moonbase",
317
+			LocalName:     "example.com:8000/private/moonbase",
318
+			CanonicalName: "example.com:8000/private/moonbase",
319
+			Official:      false,
320
+		},
321
+		"example.com:8000/privatebase": {
322
+			Index: &IndexInfo{
323
+				Name:     "example.com:8000",
324
+				Official: false,
325
+			},
326
+			RemoteName:    "privatebase",
327
+			LocalName:     "example.com:8000/privatebase",
328
+			CanonicalName: "example.com:8000/privatebase",
329
+			Official:      false,
330
+		},
331
+		"localhost/private/moonbase": {
332
+			Index: &IndexInfo{
333
+				Name:     "localhost",
334
+				Official: false,
335
+			},
336
+			RemoteName:    "private/moonbase",
337
+			LocalName:     "localhost/private/moonbase",
338
+			CanonicalName: "localhost/private/moonbase",
339
+			Official:      false,
340
+		},
341
+		"localhost/privatebase": {
342
+			Index: &IndexInfo{
343
+				Name:     "localhost",
344
+				Official: false,
345
+			},
346
+			RemoteName:    "privatebase",
347
+			LocalName:     "localhost/privatebase",
348
+			CanonicalName: "localhost/privatebase",
349
+			Official:      false,
350
+		},
351
+		IndexServerName() + "/public/moonbase": {
352
+			Index: &IndexInfo{
353
+				Name:     IndexServerName(),
354
+				Official: true,
355
+			},
356
+			RemoteName:    "public/moonbase",
357
+			LocalName:     "public/moonbase",
358
+			CanonicalName: "public/moonbase",
359
+			Official:      false,
360
+		},
361
+		"index." + IndexServerName() + "/public/moonbase": {
362
+			Index: &IndexInfo{
363
+				Name:     IndexServerName(),
364
+				Official: true,
365
+			},
366
+			RemoteName:    "public/moonbase",
367
+			LocalName:     "public/moonbase",
368
+			CanonicalName: "public/moonbase",
369
+			Official:      false,
370
+		},
371
+		IndexServerName() + "/public/moonbase": {
372
+			Index: &IndexInfo{
373
+				Name:     IndexServerName(),
374
+				Official: true,
375
+			},
376
+			RemoteName:    "public/moonbase",
377
+			LocalName:     "public/moonbase",
378
+			CanonicalName: "public/moonbase",
379
+			Official:      false,
380
+		},
381
+		"ubuntu-12.04-base": {
382
+			Index: &IndexInfo{
383
+				Name:     IndexServerName(),
384
+				Official: true,
385
+			},
386
+			RemoteName:    "library/ubuntu-12.04-base",
387
+			LocalName:     "ubuntu-12.04-base",
388
+			CanonicalName: "ubuntu-12.04-base",
389
+			Official:      true,
390
+		},
391
+		IndexServerName() + "/ubuntu-12.04-base": {
392
+			Index: &IndexInfo{
393
+				Name:     IndexServerName(),
394
+				Official: true,
395
+			},
396
+			RemoteName:    "library/ubuntu-12.04-base",
397
+			LocalName:     "ubuntu-12.04-base",
398
+			CanonicalName: "ubuntu-12.04-base",
399
+			Official:      true,
400
+		},
401
+		IndexServerName() + "/ubuntu-12.04-base": {
402
+			Index: &IndexInfo{
403
+				Name:     IndexServerName(),
404
+				Official: true,
405
+			},
406
+			RemoteName:    "library/ubuntu-12.04-base",
407
+			LocalName:     "ubuntu-12.04-base",
408
+			CanonicalName: "ubuntu-12.04-base",
409
+			Official:      true,
410
+		},
411
+		"index." + IndexServerName() + "/ubuntu-12.04-base": {
412
+			Index: &IndexInfo{
413
+				Name:     IndexServerName(),
414
+				Official: true,
415
+			},
416
+			RemoteName:    "library/ubuntu-12.04-base",
417
+			LocalName:     "ubuntu-12.04-base",
418
+			CanonicalName: "ubuntu-12.04-base",
419
+			Official:      true,
420
+		},
421
+	}
422
+
423
+	for reposName, expectedRepoInfo := range expectedRepoInfos {
424
+		repoInfo, err := ParseRepositoryInfo(reposName)
425
+		if err != nil {
426
+			t.Error(err)
427
+		} else {
428
+			checkEqual(t, repoInfo.Index.Name, expectedRepoInfo.Index.Name, reposName)
429
+			checkEqual(t, repoInfo.RemoteName, expectedRepoInfo.RemoteName, reposName)
430
+			checkEqual(t, repoInfo.LocalName, expectedRepoInfo.LocalName, reposName)
431
+			checkEqual(t, repoInfo.CanonicalName, expectedRepoInfo.CanonicalName, reposName)
432
+			checkEqual(t, repoInfo.Index.Official, expectedRepoInfo.Index.Official, reposName)
433
+			checkEqual(t, repoInfo.Official, expectedRepoInfo.Official, reposName)
434
+		}
435
+	}
436
+}
437
+
438
+func TestNewIndexInfo(t *testing.T) {
439
+	testIndexInfo := func(config *ServiceConfig, expectedIndexInfos map[string]*IndexInfo) {
440
+		for indexName, expectedIndexInfo := range expectedIndexInfos {
441
+			index, err := config.NewIndexInfo(indexName)
442
+			if err != nil {
443
+				t.Fatal(err)
444
+			} else {
445
+				checkEqual(t, index.Name, expectedIndexInfo.Name, indexName+" name")
446
+				checkEqual(t, index.Official, expectedIndexInfo.Official, indexName+" is official")
447
+				checkEqual(t, index.Secure, expectedIndexInfo.Secure, indexName+" is secure")
448
+				checkEqual(t, len(index.Mirrors), len(expectedIndexInfo.Mirrors), indexName+" mirrors")
449
+			}
450
+		}
451
+	}
452
+
453
+	config := NewServiceConfig(nil)
454
+	noMirrors := make([]string, 0)
455
+	expectedIndexInfos := map[string]*IndexInfo{
456
+		IndexServerName(): {
457
+			Name:     IndexServerName(),
458
+			Official: true,
459
+			Secure:   true,
460
+			Mirrors:  noMirrors,
461
+		},
462
+		"index." + IndexServerName(): {
463
+			Name:     IndexServerName(),
464
+			Official: true,
465
+			Secure:   true,
466
+			Mirrors:  noMirrors,
467
+		},
468
+		"example.com": {
469
+			Name:     "example.com",
470
+			Official: false,
471
+			Secure:   true,
472
+			Mirrors:  noMirrors,
473
+		},
474
+		"127.0.0.1:5000": {
475
+			Name:     "127.0.0.1:5000",
476
+			Official: false,
477
+			Secure:   false,
478
+			Mirrors:  noMirrors,
479
+		},
480
+	}
481
+	testIndexInfo(config, expectedIndexInfos)
482
+
483
+	publicMirrors := []string{"http://mirror1.local", "http://mirror2.local"}
484
+	config = makeServiceConfig(publicMirrors, []string{"example.com"})
485
+
486
+	expectedIndexInfos = map[string]*IndexInfo{
487
+		IndexServerName(): {
488
+			Name:     IndexServerName(),
489
+			Official: true,
490
+			Secure:   true,
491
+			Mirrors:  publicMirrors,
492
+		},
493
+		"index." + IndexServerName(): {
494
+			Name:     IndexServerName(),
495
+			Official: true,
496
+			Secure:   true,
497
+			Mirrors:  publicMirrors,
498
+		},
499
+		"example.com": {
500
+			Name:     "example.com",
501
+			Official: false,
502
+			Secure:   false,
503
+			Mirrors:  noMirrors,
504
+		},
505
+		"example.com:5000": {
506
+			Name:     "example.com:5000",
507
+			Official: false,
508
+			Secure:   true,
509
+			Mirrors:  noMirrors,
510
+		},
511
+		"127.0.0.1": {
512
+			Name:     "127.0.0.1",
513
+			Official: false,
514
+			Secure:   false,
515
+			Mirrors:  noMirrors,
516
+		},
517
+		"127.0.0.1:5000": {
518
+			Name:     "127.0.0.1:5000",
519
+			Official: false,
520
+			Secure:   false,
521
+			Mirrors:  noMirrors,
522
+		},
523
+		"other.com": {
524
+			Name:     "other.com",
525
+			Official: false,
526
+			Secure:   true,
527
+			Mirrors:  noMirrors,
528
+		},
529
+	}
530
+	testIndexInfo(config, expectedIndexInfos)
531
+
532
+	config = makeServiceConfig(nil, []string{"42.42.0.0/16"})
533
+	expectedIndexInfos = map[string]*IndexInfo{
534
+		"example.com": {
535
+			Name:     "example.com",
536
+			Official: false,
537
+			Secure:   false,
538
+			Mirrors:  noMirrors,
539
+		},
540
+		"example.com:5000": {
541
+			Name:     "example.com:5000",
542
+			Official: false,
543
+			Secure:   false,
544
+			Mirrors:  noMirrors,
545
+		},
546
+		"127.0.0.1": {
547
+			Name:     "127.0.0.1",
548
+			Official: false,
549
+			Secure:   false,
550
+			Mirrors:  noMirrors,
551
+		},
552
+		"127.0.0.1:5000": {
553
+			Name:     "127.0.0.1:5000",
554
+			Official: false,
555
+			Secure:   false,
556
+			Mirrors:  noMirrors,
557
+		},
558
+		"other.com": {
559
+			Name:     "other.com",
560
+			Official: false,
561
+			Secure:   true,
562
+			Mirrors:  noMirrors,
563
+		},
180 564
 	}
181
-	assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be "+IndexServerAddress())
182
-	assertEqual(t, repo, "ubuntu-12.04-base", "Expected endpoint to be ubuntu-12.04-base")
565
+	testIndexInfo(config, expectedIndexInfos)
183 566
 }
184 567
 
185 568
 func TestPushRegistryTag(t *testing.T) {
... ...
@@ -232,7 +738,7 @@ func TestSearchRepositories(t *testing.T) {
232 232
 	assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' a ot hae 42 stars")
233 233
 }
234 234
 
235
-func TestValidRepositoryName(t *testing.T) {
235
+func TestValidRemoteName(t *testing.T) {
236 236
 	validRepositoryNames := []string{
237 237
 		// Sanity check.
238 238
 		"docker/docker",
... ...
@@ -247,7 +753,7 @@ func TestValidRepositoryName(t *testing.T) {
247 247
 		"____/____",
248 248
 	}
249 249
 	for _, repositoryName := range validRepositoryNames {
250
-		if err := validateRepositoryName(repositoryName); err != nil {
250
+		if err := validateRemoteName(repositoryName); err != nil {
251 251
 			t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err)
252 252
 		}
253 253
 	}
... ...
@@ -277,7 +783,7 @@ func TestValidRepositoryName(t *testing.T) {
277 277
 		"docker/",
278 278
 	}
279 279
 	for _, repositoryName := range invalidRepositoryNames {
280
-		if err := validateRepositoryName(repositoryName); err == nil {
280
+		if err := validateRemoteName(repositoryName); err == nil {
281 281
 			t.Errorf("Repository name should be invalid: %v", repositoryName)
282 282
 		}
283 283
 	}
... ...
@@ -350,13 +856,13 @@ func TestAddRequiredHeadersToRedirectedRequests(t *testing.T) {
350 350
 	}
351 351
 }
352 352
 
353
-func TestIsSecure(t *testing.T) {
353
+func TestIsSecureIndex(t *testing.T) {
354 354
 	tests := []struct {
355 355
 		addr               string
356 356
 		insecureRegistries []string
357 357
 		expected           bool
358 358
 	}{
359
-		{IndexServerURL.Host, nil, true},
359
+		{IndexServerName(), nil, true},
360 360
 		{"example.com", []string{}, true},
361 361
 		{"example.com", []string{"example.com"}, false},
362 362
 		{"localhost", []string{"localhost:5000"}, false},
... ...
@@ -383,10 +889,9 @@ func TestIsSecure(t *testing.T) {
383 383
 		{"invalid.domain.com:5000", []string{"invalid.domain.com:5000"}, false},
384 384
 	}
385 385
 	for _, tt := range tests {
386
-		// TODO: remove this once we remove localhost insecure by default
387
-		insecureRegistries := append(tt.insecureRegistries, "127.0.0.0/8")
388
-		if sec, err := isSecure(tt.addr, insecureRegistries); err != nil || sec != tt.expected {
389
-			t.Fatalf("isSecure failed for %q %v, expected %v got %v. Error: %v", tt.addr, insecureRegistries, tt.expected, sec, err)
386
+		config := makeServiceConfig(nil, tt.insecureRegistries)
387
+		if sec := config.isSecureIndex(tt.addr); sec != tt.expected {
388
+			t.Errorf("isSecureIndex failed for %q %v, expected %v got %v", tt.addr, tt.insecureRegistries, tt.expected, sec)
390 389
 		}
391 390
 	}
392 391
 }
... ...
@@ -13,14 +13,14 @@ import (
13 13
 //  'pull': Download images from any registry (TODO)
14 14
 //  'push': Upload images to any registry (TODO)
15 15
 type Service struct {
16
-	insecureRegistries []string
16
+	Config *ServiceConfig
17 17
 }
18 18
 
19 19
 // NewService returns a new instance of Service ready to be
20 20
 // installed no an engine.
21
-func NewService(insecureRegistries []string) *Service {
21
+func NewService(options *Options) *Service {
22 22
 	return &Service{
23
-		insecureRegistries: insecureRegistries,
23
+		Config: NewServiceConfig(options),
24 24
 	}
25 25
 }
26 26
 
... ...
@@ -28,6 +28,9 @@ func NewService(insecureRegistries []string) *Service {
28 28
 func (s *Service) Install(eng *engine.Engine) error {
29 29
 	eng.Register("auth", s.Auth)
30 30
 	eng.Register("search", s.Search)
31
+	eng.Register("resolve_repository", s.ResolveRepository)
32
+	eng.Register("resolve_index", s.ResolveIndex)
33
+	eng.Register("registry_config", s.GetRegistryConfig)
31 34
 	return nil
32 35
 }
33 36
 
... ...
@@ -39,15 +42,18 @@ func (s *Service) Auth(job *engine.Job) engine.Status {
39 39
 
40 40
 	job.GetenvJson("authConfig", authConfig)
41 41
 
42
-	if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() {
43
-		endpoint, err := NewEndpoint(addr, s.insecureRegistries)
42
+	if authConfig.ServerAddress != "" {
43
+		index, err := ResolveIndexInfo(job, authConfig.ServerAddress)
44 44
 		if err != nil {
45 45
 			return job.Error(err)
46 46
 		}
47
-		if _, err := endpoint.Ping(); err != nil {
48
-			return job.Error(err)
47
+		if !index.Official {
48
+			endpoint, err := NewEndpoint(index)
49
+			if err != nil {
50
+				return job.Error(err)
51
+			}
52
+			authConfig.ServerAddress = endpoint.String()
49 53
 		}
50
-		authConfig.ServerAddress = endpoint.String()
51 54
 	}
52 55
 
53 56
 	status, err := Login(authConfig, HTTPRequestFactory(nil))
... ...
@@ -87,12 +93,12 @@ func (s *Service) Search(job *engine.Job) engine.Status {
87 87
 	job.GetenvJson("authConfig", authConfig)
88 88
 	job.GetenvJson("metaHeaders", metaHeaders)
89 89
 
90
-	hostname, term, err := ResolveRepositoryName(term)
90
+	repoInfo, err := ResolveRepositoryInfo(job, term)
91 91
 	if err != nil {
92 92
 		return job.Error(err)
93 93
 	}
94
-
95
-	endpoint, err := NewEndpoint(hostname, s.insecureRegistries)
94
+	// *TODO: Search multiple indexes.
95
+	endpoint, err := repoInfo.GetEndpoint()
96 96
 	if err != nil {
97 97
 		return job.Error(err)
98 98
 	}
... ...
@@ -100,7 +106,7 @@ func (s *Service) Search(job *engine.Job) engine.Status {
100 100
 	if err != nil {
101 101
 		return job.Error(err)
102 102
 	}
103
-	results, err := r.SearchRepositories(term)
103
+	results, err := r.SearchRepositories(repoInfo.GetSearchTerm())
104 104
 	if err != nil {
105 105
 		return job.Error(err)
106 106
 	}
... ...
@@ -116,3 +122,92 @@ func (s *Service) Search(job *engine.Job) engine.Status {
116 116
 	}
117 117
 	return engine.StatusOK
118 118
 }
119
+
120
+// ResolveRepository splits a repository name into its components
121
+// and configuration of the associated registry.
122
+func (s *Service) ResolveRepository(job *engine.Job) engine.Status {
123
+	var (
124
+		reposName = job.Args[0]
125
+	)
126
+
127
+	repoInfo, err := s.Config.NewRepositoryInfo(reposName)
128
+	if err != nil {
129
+		return job.Error(err)
130
+	}
131
+
132
+	out := engine.Env{}
133
+	err = out.SetJson("repository", repoInfo)
134
+	if err != nil {
135
+		return job.Error(err)
136
+	}
137
+	out.WriteTo(job.Stdout)
138
+
139
+	return engine.StatusOK
140
+}
141
+
142
+// Convenience wrapper for calling resolve_repository Job from a running job.
143
+func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*RepositoryInfo, error) {
144
+	job := jobContext.Eng.Job("resolve_repository", reposName)
145
+	env, err := job.Stdout.AddEnv()
146
+	if err != nil {
147
+		return nil, err
148
+	}
149
+	if err := job.Run(); err != nil {
150
+		return nil, err
151
+	}
152
+	info := RepositoryInfo{}
153
+	if err := env.GetJson("repository", &info); err != nil {
154
+		return nil, err
155
+	}
156
+	return &info, nil
157
+}
158
+
159
+// ResolveIndex takes indexName and returns index info
160
+func (s *Service) ResolveIndex(job *engine.Job) engine.Status {
161
+	var (
162
+		indexName = job.Args[0]
163
+	)
164
+
165
+	index, err := s.Config.NewIndexInfo(indexName)
166
+	if err != nil {
167
+		return job.Error(err)
168
+	}
169
+
170
+	out := engine.Env{}
171
+	err = out.SetJson("index", index)
172
+	if err != nil {
173
+		return job.Error(err)
174
+	}
175
+	out.WriteTo(job.Stdout)
176
+
177
+	return engine.StatusOK
178
+}
179
+
180
+// Convenience wrapper for calling resolve_index Job from a running job.
181
+func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, error) {
182
+	job := jobContext.Eng.Job("resolve_index", indexName)
183
+	env, err := job.Stdout.AddEnv()
184
+	if err != nil {
185
+		return nil, err
186
+	}
187
+	if err := job.Run(); err != nil {
188
+		return nil, err
189
+	}
190
+	info := IndexInfo{}
191
+	if err := env.GetJson("index", &info); err != nil {
192
+		return nil, err
193
+	}
194
+	return &info, nil
195
+}
196
+
197
+// GetRegistryConfig returns current registry configuration.
198
+func (s *Service) GetRegistryConfig(job *engine.Job) engine.Status {
199
+	out := engine.Env{}
200
+	err := out.SetJson("config", s.Config)
201
+	if err != nil {
202
+		return job.Error(err)
203
+	}
204
+	out.WriteTo(job.Stdout)
205
+
206
+	return engine.StatusOK
207
+}
... ...
@@ -65,3 +65,44 @@ const (
65 65
 	APIVersion1 = iota + 1
66 66
 	APIVersion2
67 67
 )
68
+
69
+// RepositoryInfo Examples:
70
+// {
71
+//   "Index" : {
72
+//     "Name" : "docker.io",
73
+//     "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"],
74
+//     "Secure" : true,
75
+//     "Official" : true,
76
+//   },
77
+//   "RemoteName" : "library/debian",
78
+//   "LocalName" : "debian",
79
+//   "CanonicalName" : "docker.io/debian"
80
+//   "Official" : true,
81
+// }
82
+
83
+// {
84
+//   "Index" : {
85
+//     "Name" : "127.0.0.1:5000",
86
+//     "Mirrors" : [],
87
+//     "Secure" : false,
88
+//     "Official" : false,
89
+//   },
90
+//   "RemoteName" : "user/repo",
91
+//   "LocalName" : "127.0.0.1:5000/user/repo",
92
+//   "CanonicalName" : "127.0.0.1:5000/user/repo",
93
+//   "Official" : false,
94
+// }
95
+type IndexInfo struct {
96
+	Name     string
97
+	Mirrors  []string
98
+	Secure   bool
99
+	Official bool
100
+}
101
+
102
+type RepositoryInfo struct {
103
+	Index         *IndexInfo
104
+	RemoteName    string
105
+	LocalName     string
106
+	CanonicalName string
107
+	Official      bool
108
+}
... ...
@@ -134,6 +134,10 @@ func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) {
134 134
 	self.decorators = append(self.decorators, d...)
135 135
 }
136 136
 
137
+func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator {
138
+	return self.decorators
139
+}
140
+
137 141
 // NewRequest() creates a new *http.Request,
138 142
 // applies all decorators in the HTTPRequestFactory on the request,
139 143
 // then applies decorators provided by d on the request.