Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes <guillaume@charmes.net> (github: creack)
Guillaume J. Charmes authored on 2014/03/11 09:16:58... | ... |
@@ -8,7 +8,6 @@ import ( |
8 | 8 |
"errors" |
9 | 9 |
"fmt" |
10 | 10 |
"github.com/dotcloud/docker/archive" |
11 |
- "github.com/dotcloud/docker/auth" |
|
12 | 11 |
"github.com/dotcloud/docker/dockerversion" |
13 | 12 |
"github.com/dotcloud/docker/engine" |
14 | 13 |
"github.com/dotcloud/docker/nat" |
... | ... |
@@ -229,7 +228,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { |
229 | 229 |
|
230 | 230 |
// 'docker login': login / register a user to registry service. |
231 | 231 |
func (cli *DockerCli) CmdLogin(args ...string) error { |
232 |
- cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.") |
|
232 |
+ cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.") |
|
233 | 233 |
|
234 | 234 |
var username, password, email string |
235 | 235 |
|
... | ... |
@@ -240,7 +239,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { |
240 | 240 |
if err != nil { |
241 | 241 |
return nil |
242 | 242 |
} |
243 |
- serverAddress := auth.IndexServerAddress() |
|
243 |
+ serverAddress := registry.IndexServerAddress() |
|
244 | 244 |
if len(cmd.Args()) > 0 { |
245 | 245 |
serverAddress = cmd.Arg(0) |
246 | 246 |
} |
... | ... |
@@ -266,7 +265,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { |
266 | 266 |
cli.LoadConfigFile() |
267 | 267 |
authconfig, ok := cli.configFile.Configs[serverAddress] |
268 | 268 |
if !ok { |
269 |
- authconfig = auth.AuthConfig{} |
|
269 |
+ authconfig = registry.AuthConfig{} |
|
270 | 270 |
} |
271 | 271 |
|
272 | 272 |
if username == "" { |
... | ... |
@@ -311,7 +310,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { |
311 | 311 |
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) |
312 | 312 |
if statusCode == 401 { |
313 | 313 |
delete(cli.configFile.Configs, serverAddress) |
314 |
- auth.SaveConfig(cli.configFile) |
|
314 |
+ registry.SaveConfig(cli.configFile) |
|
315 | 315 |
return err |
316 | 316 |
} |
317 | 317 |
if err != nil { |
... | ... |
@@ -320,10 +319,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error { |
320 | 320 |
var out2 engine.Env |
321 | 321 |
err = out2.Decode(stream) |
322 | 322 |
if err != nil { |
323 |
- cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) |
|
323 |
+ cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME")) |
|
324 | 324 |
return err |
325 | 325 |
} |
326 |
- auth.SaveConfig(cli.configFile) |
|
326 |
+ registry.SaveConfig(cli.configFile) |
|
327 | 327 |
if out2.Get("Status") != "" { |
328 | 328 |
fmt.Fprintf(cli.out, "%s\n", out2.Get("Status")) |
329 | 329 |
} |
... | ... |
@@ -1008,7 +1007,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { |
1008 | 1008 |
// Custom repositories can have different rules, and we must also |
1009 | 1009 |
// allow pushing by image ID. |
1010 | 1010 |
if len(strings.SplitN(name, "/", 2)) == 1 { |
1011 |
- username := cli.configFile.Configs[auth.IndexServerAddress()].Username |
|
1011 |
+ username := cli.configFile.Configs[registry.IndexServerAddress()].Username |
|
1012 | 1012 |
if username == "" { |
1013 | 1013 |
username = "<user>" |
1014 | 1014 |
} |
... | ... |
@@ -1016,7 +1015,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { |
1016 | 1016 |
} |
1017 | 1017 |
|
1018 | 1018 |
v := url.Values{} |
1019 |
- push := func(authConfig auth.AuthConfig) error { |
|
1019 |
+ push := func(authConfig registry.AuthConfig) error { |
|
1020 | 1020 |
buf, err := json.Marshal(authConfig) |
1021 | 1021 |
if err != nil { |
1022 | 1022 |
return err |
... | ... |
@@ -1075,7 +1074,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { |
1075 | 1075 |
v.Set("fromImage", remote) |
1076 | 1076 |
v.Set("tag", *tag) |
1077 | 1077 |
|
1078 |
- pull := func(authConfig auth.AuthConfig) error { |
|
1078 |
+ pull := func(authConfig registry.AuthConfig) error { |
|
1079 | 1079 |
buf, err := json.Marshal(authConfig) |
1080 | 1080 |
if err != nil { |
1081 | 1081 |
return err |
... | ... |
@@ -2058,8 +2057,8 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b |
2058 | 2058 |
if passAuthInfo { |
2059 | 2059 |
cli.LoadConfigFile() |
2060 | 2060 |
// Resolve the Auth config relevant for this server |
2061 |
- authConfig := cli.configFile.ResolveAuthConfig(auth.IndexServerAddress()) |
|
2062 |
- getHeaders := func(authConfig auth.AuthConfig) (map[string][]string, error) { |
|
2061 |
+ authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress()) |
|
2062 |
+ getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) { |
|
2063 | 2063 |
buf, err := json.Marshal(authConfig) |
2064 | 2064 |
if err != nil { |
2065 | 2065 |
return nil, err |
... | ... |
@@ -2340,7 +2339,7 @@ func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet |
2340 | 2340 |
} |
2341 | 2341 |
|
2342 | 2342 |
func (cli *DockerCli) LoadConfigFile() (err error) { |
2343 |
- cli.configFile, err = auth.LoadConfig(os.Getenv("HOME")) |
|
2343 |
+ cli.configFile, err = registry.LoadConfig(os.Getenv("HOME")) |
|
2344 | 2344 |
if err != nil { |
2345 | 2345 |
fmt.Fprintf(cli.err, "WARNING: %s\n", err) |
2346 | 2346 |
} |
... | ... |
@@ -2422,7 +2421,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc |
2422 | 2422 |
type DockerCli struct { |
2423 | 2423 |
proto string |
2424 | 2424 |
addr string |
2425 |
- configFile *auth.ConfigFile |
|
2425 |
+ configFile *registry.ConfigFile |
|
2426 | 2426 |
in io.ReadCloser |
2427 | 2427 |
out io.Writer |
2428 | 2428 |
err io.Writer |
... | ... |
@@ -8,12 +8,12 @@ import ( |
8 | 8 |
"encoding/json" |
9 | 9 |
"expvar" |
10 | 10 |
"fmt" |
11 |
- "github.com/dotcloud/docker/auth" |
|
12 | 11 |
"github.com/dotcloud/docker/engine" |
13 | 12 |
"github.com/dotcloud/docker/pkg/listenbuffer" |
14 | 13 |
"github.com/dotcloud/docker/pkg/systemd" |
15 | 14 |
"github.com/dotcloud/docker/pkg/user" |
16 | 15 |
"github.com/dotcloud/docker/pkg/version" |
16 |
+ "github.com/dotcloud/docker/registry" |
|
17 | 17 |
"github.com/dotcloud/docker/utils" |
18 | 18 |
"github.com/gorilla/mux" |
19 | 19 |
"io" |
... | ... |
@@ -381,13 +381,13 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon |
381 | 381 |
job *engine.Job |
382 | 382 |
) |
383 | 383 |
authEncoded := r.Header.Get("X-Registry-Auth") |
384 |
- authConfig := &auth.AuthConfig{} |
|
384 |
+ authConfig := ®istry.AuthConfig{} |
|
385 | 385 |
if authEncoded != "" { |
386 | 386 |
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
387 | 387 |
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { |
388 | 388 |
// for a pull it is not an error if no auth was given |
389 | 389 |
// to increase compatibility with the existing api it is defaulting to be empty |
390 |
- authConfig = &auth.AuthConfig{} |
|
390 |
+ authConfig = ®istry.AuthConfig{} |
|
391 | 391 |
} |
392 | 392 |
} |
393 | 393 |
if image != "" { //pull |
... | ... |
@@ -429,7 +429,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons |
429 | 429 |
} |
430 | 430 |
var ( |
431 | 431 |
authEncoded = r.Header.Get("X-Registry-Auth") |
432 |
- authConfig = &auth.AuthConfig{} |
|
432 |
+ authConfig = ®istry.AuthConfig{} |
|
433 | 433 |
metaHeaders = map[string][]string{} |
434 | 434 |
) |
435 | 435 |
|
... | ... |
@@ -438,7 +438,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons |
438 | 438 |
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { |
439 | 439 |
// for a search it is not an error if no auth was given |
440 | 440 |
// to increase compatibility with the existing api it is defaulting to be empty |
441 |
- authConfig = &auth.AuthConfig{} |
|
441 |
+ authConfig = ®istry.AuthConfig{} |
|
442 | 442 |
} |
443 | 443 |
} |
444 | 444 |
for k, v := range r.Header { |
... | ... |
@@ -494,7 +494,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response |
494 | 494 |
if err := parseForm(r); err != nil { |
495 | 495 |
return err |
496 | 496 |
} |
497 |
- authConfig := &auth.AuthConfig{} |
|
497 |
+ authConfig := ®istry.AuthConfig{} |
|
498 | 498 |
|
499 | 499 |
authEncoded := r.Header.Get("X-Registry-Auth") |
500 | 500 |
if authEncoded != "" { |
... | ... |
@@ -502,7 +502,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response |
502 | 502 |
authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) |
503 | 503 |
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { |
504 | 504 |
// to increase compatibility to existing api it is defaulting to be empty |
505 |
- authConfig = &auth.AuthConfig{} |
|
505 |
+ authConfig = ®istry.AuthConfig{} |
|
506 | 506 |
} |
507 | 507 |
} else { |
508 | 508 |
// the old format is supported for compatibility if there was no authConfig header |
... | ... |
@@ -823,9 +823,9 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite |
823 | 823 |
} |
824 | 824 |
var ( |
825 | 825 |
authEncoded = r.Header.Get("X-Registry-Auth") |
826 |
- authConfig = &auth.AuthConfig{} |
|
826 |
+ authConfig = ®istry.AuthConfig{} |
|
827 | 827 |
configFileEncoded = r.Header.Get("X-Registry-Config") |
828 |
- configFile = &auth.ConfigFile{} |
|
828 |
+ configFile = ®istry.ConfigFile{} |
|
829 | 829 |
job = eng.Job("build") |
830 | 830 |
) |
831 | 831 |
|
... | ... |
@@ -838,7 +838,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite |
838 | 838 |
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { |
839 | 839 |
// for a pull it is not an error if no auth was given |
840 | 840 |
// to increase compatibility with the existing api it is defaulting to be empty |
841 |
- authConfig = &auth.AuthConfig{} |
|
841 |
+ authConfig = ®istry.AuthConfig{} |
|
842 | 842 |
} |
843 | 843 |
} |
844 | 844 |
|
... | ... |
@@ -847,7 +847,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite |
847 | 847 |
if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { |
848 | 848 |
// for a pull it is not an error if no auth was given |
849 | 849 |
// to increase compatibility with the existing api it is defaulting to be empty |
850 |
- configFile = &auth.ConfigFile{} |
|
850 |
+ configFile = ®istry.ConfigFile{} |
|
851 | 851 |
} |
852 | 852 |
} |
853 | 853 |
|
4 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,290 +0,0 @@ |
1 |
-package auth |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "encoding/base64" |
|
5 |
- "encoding/json" |
|
6 |
- "errors" |
|
7 |
- "fmt" |
|
8 |
- "github.com/dotcloud/docker/utils" |
|
9 |
- "io/ioutil" |
|
10 |
- "net/http" |
|
11 |
- "os" |
|
12 |
- "path" |
|
13 |
- "strings" |
|
14 |
-) |
|
15 |
- |
|
16 |
-// Where we store the config file |
|
17 |
-const CONFIGFILE = ".dockercfg" |
|
18 |
- |
|
19 |
-// Only used for user auth + account creation |
|
20 |
-const INDEXSERVER = "https://index.docker.io/v1/" |
|
21 |
- |
|
22 |
-//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" |
|
23 |
- |
|
24 |
-var ( |
|
25 |
- ErrConfigFileMissing = errors.New("The Auth config file is missing") |
|
26 |
-) |
|
27 |
- |
|
28 |
-type AuthConfig struct { |
|
29 |
- Username string `json:"username,omitempty"` |
|
30 |
- Password string `json:"password,omitempty"` |
|
31 |
- Auth string `json:"auth"` |
|
32 |
- Email string `json:"email"` |
|
33 |
- ServerAddress string `json:"serveraddress,omitempty"` |
|
34 |
-} |
|
35 |
- |
|
36 |
-type ConfigFile struct { |
|
37 |
- Configs map[string]AuthConfig `json:"configs,omitempty"` |
|
38 |
- rootPath string |
|
39 |
-} |
|
40 |
- |
|
41 |
-func IndexServerAddress() string { |
|
42 |
- return INDEXSERVER |
|
43 |
-} |
|
44 |
- |
|
45 |
-// create a base64 encoded auth string to store in config |
|
46 |
-func encodeAuth(authConfig *AuthConfig) string { |
|
47 |
- authStr := authConfig.Username + ":" + authConfig.Password |
|
48 |
- msg := []byte(authStr) |
|
49 |
- encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) |
|
50 |
- base64.StdEncoding.Encode(encoded, msg) |
|
51 |
- return string(encoded) |
|
52 |
-} |
|
53 |
- |
|
54 |
-// decode the auth string |
|
55 |
-func decodeAuth(authStr string) (string, string, error) { |
|
56 |
- decLen := base64.StdEncoding.DecodedLen(len(authStr)) |
|
57 |
- decoded := make([]byte, decLen) |
|
58 |
- authByte := []byte(authStr) |
|
59 |
- n, err := base64.StdEncoding.Decode(decoded, authByte) |
|
60 |
- if err != nil { |
|
61 |
- return "", "", err |
|
62 |
- } |
|
63 |
- if n > decLen { |
|
64 |
- return "", "", fmt.Errorf("Something went wrong decoding auth config") |
|
65 |
- } |
|
66 |
- arr := strings.SplitN(string(decoded), ":", 2) |
|
67 |
- if len(arr) != 2 { |
|
68 |
- return "", "", fmt.Errorf("Invalid auth configuration file") |
|
69 |
- } |
|
70 |
- password := strings.Trim(arr[1], "\x00") |
|
71 |
- return arr[0], password, nil |
|
72 |
-} |
|
73 |
- |
|
74 |
-// load up the auth config information and return values |
|
75 |
-// FIXME: use the internal golang config parser |
|
76 |
-func LoadConfig(rootPath string) (*ConfigFile, error) { |
|
77 |
- configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} |
|
78 |
- confFile := path.Join(rootPath, CONFIGFILE) |
|
79 |
- if _, err := os.Stat(confFile); err != nil { |
|
80 |
- return &configFile, nil //missing file is not an error |
|
81 |
- } |
|
82 |
- b, err := ioutil.ReadFile(confFile) |
|
83 |
- if err != nil { |
|
84 |
- return &configFile, err |
|
85 |
- } |
|
86 |
- |
|
87 |
- if err := json.Unmarshal(b, &configFile.Configs); err != nil { |
|
88 |
- arr := strings.Split(string(b), "\n") |
|
89 |
- if len(arr) < 2 { |
|
90 |
- return &configFile, fmt.Errorf("The Auth config file is empty") |
|
91 |
- } |
|
92 |
- authConfig := AuthConfig{} |
|
93 |
- origAuth := strings.Split(arr[0], " = ") |
|
94 |
- if len(origAuth) != 2 { |
|
95 |
- return &configFile, fmt.Errorf("Invalid Auth config file") |
|
96 |
- } |
|
97 |
- authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) |
|
98 |
- if err != nil { |
|
99 |
- return &configFile, err |
|
100 |
- } |
|
101 |
- origEmail := strings.Split(arr[1], " = ") |
|
102 |
- if len(origEmail) != 2 { |
|
103 |
- return &configFile, fmt.Errorf("Invalid Auth config file") |
|
104 |
- } |
|
105 |
- authConfig.Email = origEmail[1] |
|
106 |
- authConfig.ServerAddress = IndexServerAddress() |
|
107 |
- configFile.Configs[IndexServerAddress()] = authConfig |
|
108 |
- } else { |
|
109 |
- for k, authConfig := range configFile.Configs { |
|
110 |
- authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) |
|
111 |
- if err != nil { |
|
112 |
- return &configFile, err |
|
113 |
- } |
|
114 |
- authConfig.Auth = "" |
|
115 |
- configFile.Configs[k] = authConfig |
|
116 |
- authConfig.ServerAddress = k |
|
117 |
- } |
|
118 |
- } |
|
119 |
- return &configFile, nil |
|
120 |
-} |
|
121 |
- |
|
122 |
-// save the auth config |
|
123 |
-func SaveConfig(configFile *ConfigFile) error { |
|
124 |
- confFile := path.Join(configFile.rootPath, CONFIGFILE) |
|
125 |
- if len(configFile.Configs) == 0 { |
|
126 |
- os.Remove(confFile) |
|
127 |
- return nil |
|
128 |
- } |
|
129 |
- |
|
130 |
- configs := make(map[string]AuthConfig, len(configFile.Configs)) |
|
131 |
- for k, authConfig := range configFile.Configs { |
|
132 |
- authCopy := authConfig |
|
133 |
- |
|
134 |
- authCopy.Auth = encodeAuth(&authCopy) |
|
135 |
- authCopy.Username = "" |
|
136 |
- authCopy.Password = "" |
|
137 |
- authCopy.ServerAddress = "" |
|
138 |
- configs[k] = authCopy |
|
139 |
- } |
|
140 |
- |
|
141 |
- b, err := json.Marshal(configs) |
|
142 |
- if err != nil { |
|
143 |
- return err |
|
144 |
- } |
|
145 |
- err = ioutil.WriteFile(confFile, b, 0600) |
|
146 |
- if err != nil { |
|
147 |
- return err |
|
148 |
- } |
|
149 |
- return nil |
|
150 |
-} |
|
151 |
- |
|
152 |
-// try to register/login to the registry server |
|
153 |
-func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { |
|
154 |
- var ( |
|
155 |
- status string |
|
156 |
- reqBody []byte |
|
157 |
- err error |
|
158 |
- client = &http.Client{} |
|
159 |
- reqStatusCode = 0 |
|
160 |
- serverAddress = authConfig.ServerAddress |
|
161 |
- ) |
|
162 |
- |
|
163 |
- if serverAddress == "" { |
|
164 |
- serverAddress = IndexServerAddress() |
|
165 |
- } |
|
166 |
- |
|
167 |
- loginAgainstOfficialIndex := serverAddress == IndexServerAddress() |
|
168 |
- |
|
169 |
- // to avoid sending the server address to the server it should be removed before being marshalled |
|
170 |
- authCopy := *authConfig |
|
171 |
- authCopy.ServerAddress = "" |
|
172 |
- |
|
173 |
- jsonBody, err := json.Marshal(authCopy) |
|
174 |
- if err != nil { |
|
175 |
- return "", fmt.Errorf("Config Error: %s", err) |
|
176 |
- } |
|
177 |
- |
|
178 |
- // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. |
|
179 |
- b := strings.NewReader(string(jsonBody)) |
|
180 |
- req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) |
|
181 |
- if err != nil { |
|
182 |
- return "", fmt.Errorf("Server Error: %s", err) |
|
183 |
- } |
|
184 |
- reqStatusCode = req1.StatusCode |
|
185 |
- defer req1.Body.Close() |
|
186 |
- reqBody, err = ioutil.ReadAll(req1.Body) |
|
187 |
- if err != nil { |
|
188 |
- return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err) |
|
189 |
- } |
|
190 |
- |
|
191 |
- if reqStatusCode == 201 { |
|
192 |
- if loginAgainstOfficialIndex { |
|
193 |
- status = "Account created. Please use the confirmation link we sent" + |
|
194 |
- " to your e-mail to activate it." |
|
195 |
- } else { |
|
196 |
- status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." |
|
197 |
- } |
|
198 |
- } else if reqStatusCode == 400 { |
|
199 |
- if string(reqBody) == "\"Username or email already exists\"" { |
|
200 |
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil) |
|
201 |
- req.SetBasicAuth(authConfig.Username, authConfig.Password) |
|
202 |
- resp, err := client.Do(req) |
|
203 |
- if err != nil { |
|
204 |
- return "", err |
|
205 |
- } |
|
206 |
- defer resp.Body.Close() |
|
207 |
- body, err := ioutil.ReadAll(resp.Body) |
|
208 |
- if err != nil { |
|
209 |
- return "", err |
|
210 |
- } |
|
211 |
- if resp.StatusCode == 200 { |
|
212 |
- status = "Login Succeeded" |
|
213 |
- } else if resp.StatusCode == 401 { |
|
214 |
- return "", fmt.Errorf("Wrong login/password, please try again") |
|
215 |
- } else if resp.StatusCode == 403 { |
|
216 |
- if loginAgainstOfficialIndex { |
|
217 |
- return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.") |
|
218 |
- } |
|
219 |
- return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) |
|
220 |
- } else { |
|
221 |
- return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header) |
|
222 |
- } |
|
223 |
- } else { |
|
224 |
- return "", fmt.Errorf("Registration: %s", reqBody) |
|
225 |
- } |
|
226 |
- } else if reqStatusCode == 401 { |
|
227 |
- // This case would happen with private registries where /v1/users is |
|
228 |
- // protected, so people can use `docker login` as an auth check. |
|
229 |
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil) |
|
230 |
- req.SetBasicAuth(authConfig.Username, authConfig.Password) |
|
231 |
- resp, err := client.Do(req) |
|
232 |
- if err != nil { |
|
233 |
- return "", err |
|
234 |
- } |
|
235 |
- defer resp.Body.Close() |
|
236 |
- body, err := ioutil.ReadAll(resp.Body) |
|
237 |
- if err != nil { |
|
238 |
- return "", err |
|
239 |
- } |
|
240 |
- if resp.StatusCode == 200 { |
|
241 |
- status = "Login Succeeded" |
|
242 |
- } else if resp.StatusCode == 401 { |
|
243 |
- return "", fmt.Errorf("Wrong login/password, please try again") |
|
244 |
- } else { |
|
245 |
- return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, |
|
246 |
- resp.StatusCode, resp.Header) |
|
247 |
- } |
|
248 |
- } else { |
|
249 |
- return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) |
|
250 |
- } |
|
251 |
- return status, nil |
|
252 |
-} |
|
253 |
- |
|
254 |
-// this method matches a auth configuration to a server address or a url |
|
255 |
-func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig { |
|
256 |
- if hostname == IndexServerAddress() || len(hostname) == 0 { |
|
257 |
- // default to the index server |
|
258 |
- return config.Configs[IndexServerAddress()] |
|
259 |
- } |
|
260 |
- |
|
261 |
- // First try the happy case |
|
262 |
- if c, found := config.Configs[hostname]; found { |
|
263 |
- return c |
|
264 |
- } |
|
265 |
- |
|
266 |
- convertToHostname := func(url string) string { |
|
267 |
- stripped := url |
|
268 |
- if strings.HasPrefix(url, "http://") { |
|
269 |
- stripped = strings.Replace(url, "http://", "", 1) |
|
270 |
- } else if strings.HasPrefix(url, "https://") { |
|
271 |
- stripped = strings.Replace(url, "https://", "", 1) |
|
272 |
- } |
|
273 |
- |
|
274 |
- nameParts := strings.SplitN(stripped, "/", 2) |
|
275 |
- |
|
276 |
- return nameParts[0] |
|
277 |
- } |
|
278 |
- |
|
279 |
- // Maybe they have a legacy config file, we will iterate the keys converting |
|
280 |
- // them to the new format and testing |
|
281 |
- normalizedHostename := convertToHostname(hostname) |
|
282 |
- for registry, config := range config.Configs { |
|
283 |
- if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename { |
|
284 |
- return config |
|
285 |
- } |
|
286 |
- } |
|
287 |
- |
|
288 |
- // When all else fails, return an empty auth config |
|
289 |
- return AuthConfig{} |
|
290 |
-} |
291 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,149 +0,0 @@ |
1 |
-package auth |
|
2 |
- |
|
3 |
-import ( |
|
4 |
- "io/ioutil" |
|
5 |
- "os" |
|
6 |
- "testing" |
|
7 |
-) |
|
8 |
- |
|
9 |
-func TestEncodeAuth(t *testing.T) { |
|
10 |
- newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} |
|
11 |
- authStr := encodeAuth(newAuthConfig) |
|
12 |
- decAuthConfig := &AuthConfig{} |
|
13 |
- var err error |
|
14 |
- decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) |
|
15 |
- if err != nil { |
|
16 |
- t.Fatal(err) |
|
17 |
- } |
|
18 |
- if newAuthConfig.Username != decAuthConfig.Username { |
|
19 |
- t.Fatal("Encode Username doesn't match decoded Username") |
|
20 |
- } |
|
21 |
- if newAuthConfig.Password != decAuthConfig.Password { |
|
22 |
- t.Fatal("Encode Password doesn't match decoded Password") |
|
23 |
- } |
|
24 |
- if authStr != "a2VuOnRlc3Q=" { |
|
25 |
- t.Fatal("AuthString encoding isn't correct.") |
|
26 |
- } |
|
27 |
-} |
|
28 |
- |
|
29 |
-func setupTempConfigFile() (*ConfigFile, error) { |
|
30 |
- root, err := ioutil.TempDir("", "docker-test-auth") |
|
31 |
- if err != nil { |
|
32 |
- return nil, err |
|
33 |
- } |
|
34 |
- configFile := &ConfigFile{ |
|
35 |
- rootPath: root, |
|
36 |
- Configs: make(map[string]AuthConfig), |
|
37 |
- } |
|
38 |
- |
|
39 |
- for _, registry := range []string{"testIndex", IndexServerAddress()} { |
|
40 |
- configFile.Configs[registry] = AuthConfig{ |
|
41 |
- Username: "docker-user", |
|
42 |
- Password: "docker-pass", |
|
43 |
- Email: "docker@docker.io", |
|
44 |
- } |
|
45 |
- } |
|
46 |
- |
|
47 |
- return configFile, nil |
|
48 |
-} |
|
49 |
- |
|
50 |
-func TestSameAuthDataPostSave(t *testing.T) { |
|
51 |
- configFile, err := setupTempConfigFile() |
|
52 |
- if err != nil { |
|
53 |
- t.Fatal(err) |
|
54 |
- } |
|
55 |
- defer os.RemoveAll(configFile.rootPath) |
|
56 |
- |
|
57 |
- err = SaveConfig(configFile) |
|
58 |
- if err != nil { |
|
59 |
- t.Fatal(err) |
|
60 |
- } |
|
61 |
- |
|
62 |
- authConfig := configFile.Configs["testIndex"] |
|
63 |
- if authConfig.Username != "docker-user" { |
|
64 |
- t.Fail() |
|
65 |
- } |
|
66 |
- if authConfig.Password != "docker-pass" { |
|
67 |
- t.Fail() |
|
68 |
- } |
|
69 |
- if authConfig.Email != "docker@docker.io" { |
|
70 |
- t.Fail() |
|
71 |
- } |
|
72 |
- if authConfig.Auth != "" { |
|
73 |
- t.Fail() |
|
74 |
- } |
|
75 |
-} |
|
76 |
- |
|
77 |
-func TestResolveAuthConfigIndexServer(t *testing.T) { |
|
78 |
- configFile, err := setupTempConfigFile() |
|
79 |
- if err != nil { |
|
80 |
- t.Fatal(err) |
|
81 |
- } |
|
82 |
- defer os.RemoveAll(configFile.rootPath) |
|
83 |
- |
|
84 |
- for _, registry := range []string{"", IndexServerAddress()} { |
|
85 |
- resolved := configFile.ResolveAuthConfig(registry) |
|
86 |
- if resolved != configFile.Configs[IndexServerAddress()] { |
|
87 |
- t.Fail() |
|
88 |
- } |
|
89 |
- } |
|
90 |
-} |
|
91 |
- |
|
92 |
-func TestResolveAuthConfigFullURL(t *testing.T) { |
|
93 |
- configFile, err := setupTempConfigFile() |
|
94 |
- if err != nil { |
|
95 |
- t.Fatal(err) |
|
96 |
- } |
|
97 |
- defer os.RemoveAll(configFile.rootPath) |
|
98 |
- |
|
99 |
- registryAuth := AuthConfig{ |
|
100 |
- Username: "foo-user", |
|
101 |
- Password: "foo-pass", |
|
102 |
- Email: "foo@example.com", |
|
103 |
- } |
|
104 |
- localAuth := AuthConfig{ |
|
105 |
- Username: "bar-user", |
|
106 |
- Password: "bar-pass", |
|
107 |
- Email: "bar@example.com", |
|
108 |
- } |
|
109 |
- configFile.Configs["https://registry.example.com/v1/"] = registryAuth |
|
110 |
- configFile.Configs["http://localhost:8000/v1/"] = localAuth |
|
111 |
- configFile.Configs["registry.com"] = registryAuth |
|
112 |
- |
|
113 |
- validRegistries := map[string][]string{ |
|
114 |
- "https://registry.example.com/v1/": { |
|
115 |
- "https://registry.example.com/v1/", |
|
116 |
- "http://registry.example.com/v1/", |
|
117 |
- "registry.example.com", |
|
118 |
- "registry.example.com/v1/", |
|
119 |
- }, |
|
120 |
- "http://localhost:8000/v1/": { |
|
121 |
- "https://localhost:8000/v1/", |
|
122 |
- "http://localhost:8000/v1/", |
|
123 |
- "localhost:8000", |
|
124 |
- "localhost:8000/v1/", |
|
125 |
- }, |
|
126 |
- "registry.com": { |
|
127 |
- "https://registry.com/v1/", |
|
128 |
- "http://registry.com/v1/", |
|
129 |
- "registry.com", |
|
130 |
- "registry.com/v1/", |
|
131 |
- }, |
|
132 |
- } |
|
133 |
- |
|
134 |
- for configKey, registries := range validRegistries { |
|
135 |
- 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 |
- } |
|
144 |
- if resolved.Email != configured.Email { |
|
145 |
- t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) |
|
146 |
- } |
|
147 |
- } |
|
148 |
- } |
|
149 |
-} |
... | ... |
@@ -7,7 +7,6 @@ import ( |
7 | 7 |
"errors" |
8 | 8 |
"fmt" |
9 | 9 |
"github.com/dotcloud/docker/archive" |
10 |
- "github.com/dotcloud/docker/auth" |
|
11 | 10 |
"github.com/dotcloud/docker/registry" |
12 | 11 |
"github.com/dotcloud/docker/runconfig" |
13 | 12 |
"github.com/dotcloud/docker/runtime" |
... | ... |
@@ -49,8 +48,8 @@ type buildFile struct { |
49 | 49 |
utilizeCache bool |
50 | 50 |
rm bool |
51 | 51 |
|
52 |
- authConfig *auth.AuthConfig |
|
53 |
- configFile *auth.ConfigFile |
|
52 |
+ authConfig *registry.AuthConfig |
|
53 |
+ configFile *registry.ConfigFile |
|
54 | 54 |
|
55 | 55 |
tmpContainers map[string]struct{} |
56 | 56 |
tmpImages map[string]struct{} |
... | ... |
@@ -793,7 +792,7 @@ func (b *buildFile) BuildStep(name, expression string) error { |
793 | 793 |
return nil |
794 | 794 |
} |
795 | 795 |
|
796 |
-func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile { |
|
796 |
+func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile { |
|
797 | 797 |
return &buildFile{ |
798 | 798 |
runtime: srv.runtime, |
799 | 799 |
srv: srv, |
... | ... |
@@ -4,7 +4,7 @@ import ( |
4 | 4 |
"crypto/rand" |
5 | 5 |
"encoding/hex" |
6 | 6 |
"fmt" |
7 |
- "github.com/dotcloud/docker/auth" |
|
7 |
+ "github.com/dotcloud/docker/registry" |
|
8 | 8 |
"os" |
9 | 9 |
"strings" |
10 | 10 |
"testing" |
... | ... |
@@ -18,13 +18,13 @@ import ( |
18 | 18 |
func TestLogin(t *testing.T) { |
19 | 19 |
os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") |
20 | 20 |
defer os.Setenv("DOCKER_INDEX_URL", "") |
21 |
- authConfig := &auth.AuthConfig{ |
|
21 |
+ authConfig := ®istry.AuthConfig{ |
|
22 | 22 |
Username: "unittester", |
23 | 23 |
Password: "surlautrerivejetattendrai", |
24 | 24 |
Email: "noise+unittester@docker.com", |
25 | 25 |
ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", |
26 | 26 |
} |
27 |
- status, err := auth.Login(authConfig, nil) |
|
27 |
+ status, err := registry.Login(authConfig, nil) |
|
28 | 28 |
if err != nil { |
29 | 29 |
t.Fatal(err) |
30 | 30 |
} |
... | ... |
@@ -41,13 +41,13 @@ func TestCreateAccount(t *testing.T) { |
41 | 41 |
} |
42 | 42 |
token := hex.EncodeToString(tokenBuffer)[:12] |
43 | 43 |
username := "ut" + token |
44 |
- authConfig := &auth.AuthConfig{ |
|
44 |
+ authConfig := ®istry.AuthConfig{ |
|
45 | 45 |
Username: username, |
46 | 46 |
Password: "test42", |
47 | 47 |
Email: fmt.Sprintf("docker-ut+%s@example.com", token), |
48 | 48 |
ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", |
49 | 49 |
} |
50 |
- status, err := auth.Login(authConfig, nil) |
|
50 |
+ status, err := registry.Login(authConfig, nil) |
|
51 | 51 |
if err != nil { |
52 | 52 |
t.Fatal(err) |
53 | 53 |
} |
... | ... |
@@ -59,7 +59,7 @@ func TestCreateAccount(t *testing.T) { |
59 | 59 |
t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) |
60 | 60 |
} |
61 | 61 |
|
62 |
- status, err = auth.Login(authConfig, nil) |
|
62 |
+ status, err = registry.Login(authConfig, nil) |
|
63 | 63 |
if err == nil { |
64 | 64 |
t.Fatalf("Expected error but found nil instead") |
65 | 65 |
} |
66 | 66 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,290 @@ |
0 |
+package registry |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "encoding/base64" |
|
4 |
+ "encoding/json" |
|
5 |
+ "errors" |
|
6 |
+ "fmt" |
|
7 |
+ "github.com/dotcloud/docker/utils" |
|
8 |
+ "io/ioutil" |
|
9 |
+ "net/http" |
|
10 |
+ "os" |
|
11 |
+ "path" |
|
12 |
+ "strings" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+// Where we store the config file |
|
16 |
+const CONFIGFILE = ".dockercfg" |
|
17 |
+ |
|
18 |
+// Only used for user auth + account creation |
|
19 |
+const INDEXSERVER = "https://index.docker.io/v1/" |
|
20 |
+ |
|
21 |
+//const INDEXSERVER = "https://indexstaging-docker.dotcloud.com/v1/" |
|
22 |
+ |
|
23 |
+var ( |
|
24 |
+ ErrConfigFileMissing = errors.New("The Auth config file is missing") |
|
25 |
+) |
|
26 |
+ |
|
27 |
+type AuthConfig struct { |
|
28 |
+ Username string `json:"username,omitempty"` |
|
29 |
+ Password string `json:"password,omitempty"` |
|
30 |
+ Auth string `json:"auth"` |
|
31 |
+ Email string `json:"email"` |
|
32 |
+ ServerAddress string `json:"serveraddress,omitempty"` |
|
33 |
+} |
|
34 |
+ |
|
35 |
+type ConfigFile struct { |
|
36 |
+ Configs map[string]AuthConfig `json:"configs,omitempty"` |
|
37 |
+ rootPath string |
|
38 |
+} |
|
39 |
+ |
|
40 |
+func IndexServerAddress() string { |
|
41 |
+ return INDEXSERVER |
|
42 |
+} |
|
43 |
+ |
|
44 |
+// create a base64 encoded auth string to store in config |
|
45 |
+func encodeAuth(authConfig *AuthConfig) string { |
|
46 |
+ authStr := authConfig.Username + ":" + authConfig.Password |
|
47 |
+ msg := []byte(authStr) |
|
48 |
+ encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) |
|
49 |
+ base64.StdEncoding.Encode(encoded, msg) |
|
50 |
+ return string(encoded) |
|
51 |
+} |
|
52 |
+ |
|
53 |
+// decode the auth string |
|
54 |
+func decodeAuth(authStr string) (string, string, error) { |
|
55 |
+ decLen := base64.StdEncoding.DecodedLen(len(authStr)) |
|
56 |
+ decoded := make([]byte, decLen) |
|
57 |
+ authByte := []byte(authStr) |
|
58 |
+ n, err := base64.StdEncoding.Decode(decoded, authByte) |
|
59 |
+ if err != nil { |
|
60 |
+ return "", "", err |
|
61 |
+ } |
|
62 |
+ if n > decLen { |
|
63 |
+ return "", "", fmt.Errorf("Something went wrong decoding auth config") |
|
64 |
+ } |
|
65 |
+ arr := strings.SplitN(string(decoded), ":", 2) |
|
66 |
+ if len(arr) != 2 { |
|
67 |
+ return "", "", fmt.Errorf("Invalid auth configuration file") |
|
68 |
+ } |
|
69 |
+ password := strings.Trim(arr[1], "\x00") |
|
70 |
+ return arr[0], password, nil |
|
71 |
+} |
|
72 |
+ |
|
73 |
+// load up the auth config information and return values |
|
74 |
+// FIXME: use the internal golang config parser |
|
75 |
+func LoadConfig(rootPath string) (*ConfigFile, error) { |
|
76 |
+ configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} |
|
77 |
+ confFile := path.Join(rootPath, CONFIGFILE) |
|
78 |
+ if _, err := os.Stat(confFile); err != nil { |
|
79 |
+ return &configFile, nil //missing file is not an error |
|
80 |
+ } |
|
81 |
+ b, err := ioutil.ReadFile(confFile) |
|
82 |
+ if err != nil { |
|
83 |
+ return &configFile, err |
|
84 |
+ } |
|
85 |
+ |
|
86 |
+ if err := json.Unmarshal(b, &configFile.Configs); err != nil { |
|
87 |
+ arr := strings.Split(string(b), "\n") |
|
88 |
+ if len(arr) < 2 { |
|
89 |
+ return &configFile, fmt.Errorf("The Auth config file is empty") |
|
90 |
+ } |
|
91 |
+ authConfig := AuthConfig{} |
|
92 |
+ origAuth := strings.Split(arr[0], " = ") |
|
93 |
+ if len(origAuth) != 2 { |
|
94 |
+ return &configFile, fmt.Errorf("Invalid Auth config file") |
|
95 |
+ } |
|
96 |
+ authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) |
|
97 |
+ if err != nil { |
|
98 |
+ return &configFile, err |
|
99 |
+ } |
|
100 |
+ origEmail := strings.Split(arr[1], " = ") |
|
101 |
+ if len(origEmail) != 2 { |
|
102 |
+ return &configFile, fmt.Errorf("Invalid Auth config file") |
|
103 |
+ } |
|
104 |
+ authConfig.Email = origEmail[1] |
|
105 |
+ authConfig.ServerAddress = IndexServerAddress() |
|
106 |
+ configFile.Configs[IndexServerAddress()] = authConfig |
|
107 |
+ } else { |
|
108 |
+ for k, authConfig := range configFile.Configs { |
|
109 |
+ authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) |
|
110 |
+ if err != nil { |
|
111 |
+ return &configFile, err |
|
112 |
+ } |
|
113 |
+ authConfig.Auth = "" |
|
114 |
+ configFile.Configs[k] = authConfig |
|
115 |
+ authConfig.ServerAddress = k |
|
116 |
+ } |
|
117 |
+ } |
|
118 |
+ return &configFile, nil |
|
119 |
+} |
|
120 |
+ |
|
121 |
+// save the auth config |
|
122 |
+func SaveConfig(configFile *ConfigFile) error { |
|
123 |
+ confFile := path.Join(configFile.rootPath, CONFIGFILE) |
|
124 |
+ if len(configFile.Configs) == 0 { |
|
125 |
+ os.Remove(confFile) |
|
126 |
+ return nil |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ configs := make(map[string]AuthConfig, len(configFile.Configs)) |
|
130 |
+ for k, authConfig := range configFile.Configs { |
|
131 |
+ authCopy := authConfig |
|
132 |
+ |
|
133 |
+ authCopy.Auth = encodeAuth(&authCopy) |
|
134 |
+ authCopy.Username = "" |
|
135 |
+ authCopy.Password = "" |
|
136 |
+ authCopy.ServerAddress = "" |
|
137 |
+ configs[k] = authCopy |
|
138 |
+ } |
|
139 |
+ |
|
140 |
+ b, err := json.Marshal(configs) |
|
141 |
+ if err != nil { |
|
142 |
+ return err |
|
143 |
+ } |
|
144 |
+ err = ioutil.WriteFile(confFile, b, 0600) |
|
145 |
+ if err != nil { |
|
146 |
+ return err |
|
147 |
+ } |
|
148 |
+ return nil |
|
149 |
+} |
|
150 |
+ |
|
151 |
+// try to register/login to the registry server |
|
152 |
+func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { |
|
153 |
+ var ( |
|
154 |
+ status string |
|
155 |
+ reqBody []byte |
|
156 |
+ err error |
|
157 |
+ client = &http.Client{} |
|
158 |
+ reqStatusCode = 0 |
|
159 |
+ serverAddress = authConfig.ServerAddress |
|
160 |
+ ) |
|
161 |
+ |
|
162 |
+ if serverAddress == "" { |
|
163 |
+ serverAddress = IndexServerAddress() |
|
164 |
+ } |
|
165 |
+ |
|
166 |
+ loginAgainstOfficialIndex := serverAddress == IndexServerAddress() |
|
167 |
+ |
|
168 |
+ // to avoid sending the server address to the server it should be removed before being marshalled |
|
169 |
+ authCopy := *authConfig |
|
170 |
+ authCopy.ServerAddress = "" |
|
171 |
+ |
|
172 |
+ jsonBody, err := json.Marshal(authCopy) |
|
173 |
+ if err != nil { |
|
174 |
+ return "", fmt.Errorf("Config Error: %s", err) |
|
175 |
+ } |
|
176 |
+ |
|
177 |
+ // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. |
|
178 |
+ b := strings.NewReader(string(jsonBody)) |
|
179 |
+ req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) |
|
180 |
+ if err != nil { |
|
181 |
+ return "", fmt.Errorf("Server Error: %s", err) |
|
182 |
+ } |
|
183 |
+ reqStatusCode = req1.StatusCode |
|
184 |
+ defer req1.Body.Close() |
|
185 |
+ reqBody, err = ioutil.ReadAll(req1.Body) |
|
186 |
+ if err != nil { |
|
187 |
+ return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err) |
|
188 |
+ } |
|
189 |
+ |
|
190 |
+ if reqStatusCode == 201 { |
|
191 |
+ if loginAgainstOfficialIndex { |
|
192 |
+ status = "Account created. Please use the confirmation link we sent" + |
|
193 |
+ " to your e-mail to activate it." |
|
194 |
+ } else { |
|
195 |
+ status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." |
|
196 |
+ } |
|
197 |
+ } else if reqStatusCode == 400 { |
|
198 |
+ if string(reqBody) == "\"Username or email already exists\"" { |
|
199 |
+ req, err := factory.NewRequest("GET", serverAddress+"users/", nil) |
|
200 |
+ req.SetBasicAuth(authConfig.Username, authConfig.Password) |
|
201 |
+ resp, err := client.Do(req) |
|
202 |
+ if err != nil { |
|
203 |
+ return "", err |
|
204 |
+ } |
|
205 |
+ defer resp.Body.Close() |
|
206 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
207 |
+ if err != nil { |
|
208 |
+ return "", err |
|
209 |
+ } |
|
210 |
+ if resp.StatusCode == 200 { |
|
211 |
+ status = "Login Succeeded" |
|
212 |
+ } else if resp.StatusCode == 401 { |
|
213 |
+ return "", fmt.Errorf("Wrong login/password, please try again") |
|
214 |
+ } else if resp.StatusCode == 403 { |
|
215 |
+ if loginAgainstOfficialIndex { |
|
216 |
+ return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.") |
|
217 |
+ } |
|
218 |
+ return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress) |
|
219 |
+ } else { |
|
220 |
+ return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header) |
|
221 |
+ } |
|
222 |
+ } else { |
|
223 |
+ return "", fmt.Errorf("Registration: %s", reqBody) |
|
224 |
+ } |
|
225 |
+ } else if reqStatusCode == 401 { |
|
226 |
+ // This case would happen with private registries where /v1/users is |
|
227 |
+ // protected, so people can use `docker login` as an auth check. |
|
228 |
+ req, err := factory.NewRequest("GET", serverAddress+"users/", nil) |
|
229 |
+ req.SetBasicAuth(authConfig.Username, authConfig.Password) |
|
230 |
+ resp, err := client.Do(req) |
|
231 |
+ if err != nil { |
|
232 |
+ return "", err |
|
233 |
+ } |
|
234 |
+ defer resp.Body.Close() |
|
235 |
+ body, err := ioutil.ReadAll(resp.Body) |
|
236 |
+ if err != nil { |
|
237 |
+ return "", err |
|
238 |
+ } |
|
239 |
+ if resp.StatusCode == 200 { |
|
240 |
+ status = "Login Succeeded" |
|
241 |
+ } else if resp.StatusCode == 401 { |
|
242 |
+ return "", fmt.Errorf("Wrong login/password, please try again") |
|
243 |
+ } else { |
|
244 |
+ return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, |
|
245 |
+ resp.StatusCode, resp.Header) |
|
246 |
+ } |
|
247 |
+ } else { |
|
248 |
+ return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) |
|
249 |
+ } |
|
250 |
+ return status, nil |
|
251 |
+} |
|
252 |
+ |
|
253 |
+// this method matches a auth configuration to a server address or a url |
|
254 |
+func (config *ConfigFile) ResolveAuthConfig(hostname string) AuthConfig { |
|
255 |
+ if hostname == IndexServerAddress() || len(hostname) == 0 { |
|
256 |
+ // default to the index server |
|
257 |
+ return config.Configs[IndexServerAddress()] |
|
258 |
+ } |
|
259 |
+ |
|
260 |
+ // First try the happy case |
|
261 |
+ if c, found := config.Configs[hostname]; found { |
|
262 |
+ return c |
|
263 |
+ } |
|
264 |
+ |
|
265 |
+ convertToHostname := func(url string) string { |
|
266 |
+ stripped := url |
|
267 |
+ if strings.HasPrefix(url, "http://") { |
|
268 |
+ stripped = strings.Replace(url, "http://", "", 1) |
|
269 |
+ } else if strings.HasPrefix(url, "https://") { |
|
270 |
+ stripped = strings.Replace(url, "https://", "", 1) |
|
271 |
+ } |
|
272 |
+ |
|
273 |
+ nameParts := strings.SplitN(stripped, "/", 2) |
|
274 |
+ |
|
275 |
+ return nameParts[0] |
|
276 |
+ } |
|
277 |
+ |
|
278 |
+ // Maybe they have a legacy config file, we will iterate the keys converting |
|
279 |
+ // them to the new format and testing |
|
280 |
+ normalizedHostename := convertToHostname(hostname) |
|
281 |
+ for registry, config := range config.Configs { |
|
282 |
+ if registryHostname := convertToHostname(registry); registryHostname == normalizedHostename { |
|
283 |
+ return config |
|
284 |
+ } |
|
285 |
+ } |
|
286 |
+ |
|
287 |
+ // When all else fails, return an empty auth config |
|
288 |
+ return AuthConfig{} |
|
289 |
+} |
0 | 290 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,149 @@ |
0 |
+package registry |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "io/ioutil" |
|
4 |
+ "os" |
|
5 |
+ "testing" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+func TestEncodeAuth(t *testing.T) { |
|
9 |
+ newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} |
|
10 |
+ authStr := encodeAuth(newAuthConfig) |
|
11 |
+ decAuthConfig := &AuthConfig{} |
|
12 |
+ var err error |
|
13 |
+ decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) |
|
14 |
+ if err != nil { |
|
15 |
+ t.Fatal(err) |
|
16 |
+ } |
|
17 |
+ if newAuthConfig.Username != decAuthConfig.Username { |
|
18 |
+ t.Fatal("Encode Username doesn't match decoded Username") |
|
19 |
+ } |
|
20 |
+ if newAuthConfig.Password != decAuthConfig.Password { |
|
21 |
+ t.Fatal("Encode Password doesn't match decoded Password") |
|
22 |
+ } |
|
23 |
+ if authStr != "a2VuOnRlc3Q=" { |
|
24 |
+ t.Fatal("AuthString encoding isn't correct.") |
|
25 |
+ } |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func setupTempConfigFile() (*ConfigFile, error) { |
|
29 |
+ root, err := ioutil.TempDir("", "docker-test-auth") |
|
30 |
+ if err != nil { |
|
31 |
+ return nil, err |
|
32 |
+ } |
|
33 |
+ configFile := &ConfigFile{ |
|
34 |
+ rootPath: root, |
|
35 |
+ Configs: make(map[string]AuthConfig), |
|
36 |
+ } |
|
37 |
+ |
|
38 |
+ for _, registry := range []string{"testIndex", IndexServerAddress()} { |
|
39 |
+ configFile.Configs[registry] = AuthConfig{ |
|
40 |
+ Username: "docker-user", |
|
41 |
+ Password: "docker-pass", |
|
42 |
+ Email: "docker@docker.io", |
|
43 |
+ } |
|
44 |
+ } |
|
45 |
+ |
|
46 |
+ return configFile, nil |
|
47 |
+} |
|
48 |
+ |
|
49 |
+func TestSameAuthDataPostSave(t *testing.T) { |
|
50 |
+ configFile, err := setupTempConfigFile() |
|
51 |
+ if err != nil { |
|
52 |
+ t.Fatal(err) |
|
53 |
+ } |
|
54 |
+ defer os.RemoveAll(configFile.rootPath) |
|
55 |
+ |
|
56 |
+ err = SaveConfig(configFile) |
|
57 |
+ if err != nil { |
|
58 |
+ t.Fatal(err) |
|
59 |
+ } |
|
60 |
+ |
|
61 |
+ authConfig := configFile.Configs["testIndex"] |
|
62 |
+ if authConfig.Username != "docker-user" { |
|
63 |
+ t.Fail() |
|
64 |
+ } |
|
65 |
+ if authConfig.Password != "docker-pass" { |
|
66 |
+ t.Fail() |
|
67 |
+ } |
|
68 |
+ if authConfig.Email != "docker@docker.io" { |
|
69 |
+ t.Fail() |
|
70 |
+ } |
|
71 |
+ if authConfig.Auth != "" { |
|
72 |
+ t.Fail() |
|
73 |
+ } |
|
74 |
+} |
|
75 |
+ |
|
76 |
+func TestResolveAuthConfigIndexServer(t *testing.T) { |
|
77 |
+ configFile, err := setupTempConfigFile() |
|
78 |
+ if err != nil { |
|
79 |
+ t.Fatal(err) |
|
80 |
+ } |
|
81 |
+ defer os.RemoveAll(configFile.rootPath) |
|
82 |
+ |
|
83 |
+ for _, registry := range []string{"", IndexServerAddress()} { |
|
84 |
+ resolved := configFile.ResolveAuthConfig(registry) |
|
85 |
+ if resolved != configFile.Configs[IndexServerAddress()] { |
|
86 |
+ t.Fail() |
|
87 |
+ } |
|
88 |
+ } |
|
89 |
+} |
|
90 |
+ |
|
91 |
+func TestResolveAuthConfigFullURL(t *testing.T) { |
|
92 |
+ configFile, err := setupTempConfigFile() |
|
93 |
+ if err != nil { |
|
94 |
+ t.Fatal(err) |
|
95 |
+ } |
|
96 |
+ defer os.RemoveAll(configFile.rootPath) |
|
97 |
+ |
|
98 |
+ registryAuth := AuthConfig{ |
|
99 |
+ Username: "foo-user", |
|
100 |
+ Password: "foo-pass", |
|
101 |
+ Email: "foo@example.com", |
|
102 |
+ } |
|
103 |
+ localAuth := AuthConfig{ |
|
104 |
+ Username: "bar-user", |
|
105 |
+ Password: "bar-pass", |
|
106 |
+ Email: "bar@example.com", |
|
107 |
+ } |
|
108 |
+ configFile.Configs["https://registry.example.com/v1/"] = registryAuth |
|
109 |
+ configFile.Configs["http://localhost:8000/v1/"] = localAuth |
|
110 |
+ configFile.Configs["registry.com"] = registryAuth |
|
111 |
+ |
|
112 |
+ validRegistries := map[string][]string{ |
|
113 |
+ "https://registry.example.com/v1/": { |
|
114 |
+ "https://registry.example.com/v1/", |
|
115 |
+ "http://registry.example.com/v1/", |
|
116 |
+ "registry.example.com", |
|
117 |
+ "registry.example.com/v1/", |
|
118 |
+ }, |
|
119 |
+ "http://localhost:8000/v1/": { |
|
120 |
+ "https://localhost:8000/v1/", |
|
121 |
+ "http://localhost:8000/v1/", |
|
122 |
+ "localhost:8000", |
|
123 |
+ "localhost:8000/v1/", |
|
124 |
+ }, |
|
125 |
+ "registry.com": { |
|
126 |
+ "https://registry.com/v1/", |
|
127 |
+ "http://registry.com/v1/", |
|
128 |
+ "registry.com", |
|
129 |
+ "registry.com/v1/", |
|
130 |
+ }, |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ for configKey, registries := range validRegistries { |
|
134 |
+ for _, registry := range registries { |
|
135 |
+ var ( |
|
136 |
+ configured AuthConfig |
|
137 |
+ ok bool |
|
138 |
+ ) |
|
139 |
+ resolved := configFile.ResolveAuthConfig(registry) |
|
140 |
+ if configured, ok = configFile.Configs[configKey]; !ok { |
|
141 |
+ t.Fail() |
|
142 |
+ } |
|
143 |
+ if resolved.Email != configured.Email { |
|
144 |
+ t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) |
|
145 |
+ } |
|
146 |
+ } |
|
147 |
+ } |
|
148 |
+} |
... | ... |
@@ -6,7 +6,6 @@ import ( |
6 | 6 |
"encoding/json" |
7 | 7 |
"errors" |
8 | 8 |
"fmt" |
9 |
- "github.com/dotcloud/docker/auth" |
|
10 | 9 |
"github.com/dotcloud/docker/utils" |
11 | 10 |
"io" |
12 | 11 |
"io/ioutil" |
... | ... |
@@ -27,7 +26,7 @@ var ( |
27 | 27 |
) |
28 | 28 |
|
29 | 29 |
func pingRegistryEndpoint(endpoint string) (bool, error) { |
30 |
- if endpoint == auth.IndexServerAddress() { |
|
30 |
+ if endpoint == IndexServerAddress() { |
|
31 | 31 |
// Skip the check, we now this one is valid |
32 | 32 |
// (and we never want to fallback to http in case of error) |
33 | 33 |
return false, nil |
... | ... |
@@ -103,7 +102,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { |
103 | 103 |
nameParts[0] != "localhost" { |
104 | 104 |
// This is a Docker Index repos (ex: samalba/hipache or ubuntu) |
105 | 105 |
err := validateRepositoryName(reposName) |
106 |
- return auth.IndexServerAddress(), reposName, err |
|
106 |
+ return IndexServerAddress(), reposName, err |
|
107 | 107 |
} |
108 | 108 |
if len(nameParts) < 2 { |
109 | 109 |
// There is a dot in repos name (and no registry address) |
... | ... |
@@ -601,7 +600,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat |
601 | 601 |
|
602 | 602 |
func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { |
603 | 603 |
utils.Debugf("Index server: %s", r.indexEndpoint) |
604 |
- u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) |
|
604 |
+ u := IndexServerAddress() + "search?q=" + url.QueryEscape(term) |
|
605 | 605 |
req, err := r.reqFactory.NewRequest("GET", u, nil) |
606 | 606 |
if err != nil { |
607 | 607 |
return nil, err |
... | ... |
@@ -627,12 +626,12 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { |
627 | 627 |
return result, err |
628 | 628 |
} |
629 | 629 |
|
630 |
-func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig { |
|
630 |
+func (r *Registry) GetAuthConfig(withPasswd bool) *AuthConfig { |
|
631 | 631 |
password := "" |
632 | 632 |
if withPasswd { |
633 | 633 |
password = r.authConfig.Password |
634 | 634 |
} |
635 |
- return &auth.AuthConfig{ |
|
635 |
+ return &AuthConfig{ |
|
636 | 636 |
Username: r.authConfig.Username, |
637 | 637 |
Password: password, |
638 | 638 |
Email: r.authConfig.Email, |
... | ... |
@@ -668,12 +667,12 @@ type ImgData struct { |
668 | 668 |
|
669 | 669 |
type Registry struct { |
670 | 670 |
client *http.Client |
671 |
- authConfig *auth.AuthConfig |
|
671 |
+ authConfig *AuthConfig |
|
672 | 672 |
reqFactory *utils.HTTPRequestFactory |
673 | 673 |
indexEndpoint string |
674 | 674 |
} |
675 | 675 |
|
676 |
-func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { |
|
676 |
+func NewRegistry(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, indexEndpoint string) (r *Registry, err error) { |
|
677 | 677 |
httpTransport := &http.Transport{ |
678 | 678 |
DisableKeepAlives: true, |
679 | 679 |
Proxy: http.ProxyFromEnvironment, |
... | ... |
@@ -693,13 +692,13 @@ func NewRegistry(authConfig *auth.AuthConfig, factory *utils.HTTPRequestFactory, |
693 | 693 |
|
694 | 694 |
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers |
695 | 695 |
// alongside our requests. |
696 |
- if indexEndpoint != auth.IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { |
|
696 |
+ if indexEndpoint != IndexServerAddress() && strings.HasPrefix(indexEndpoint, "https://") { |
|
697 | 697 |
standalone, err := pingRegistryEndpoint(indexEndpoint) |
698 | 698 |
if err != nil { |
699 | 699 |
return nil, err |
700 | 700 |
} |
701 | 701 |
if standalone { |
702 |
- utils.Debugf("Endpoint %s is eligible for private registry auth. Enabling decorator.", indexEndpoint) |
|
702 |
+ utils.Debugf("Endpoint %s is eligible for private registry registry. Enabling decorator.", indexEndpoint) |
|
703 | 703 |
dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) |
704 | 704 |
factory.AddDecorator(dec) |
705 | 705 |
} |
... | ... |
@@ -1,7 +1,6 @@ |
1 | 1 |
package registry |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "github.com/dotcloud/docker/auth" |
|
5 | 4 |
"github.com/dotcloud/docker/utils" |
6 | 5 |
"strings" |
7 | 6 |
"testing" |
... | ... |
@@ -14,7 +13,7 @@ var ( |
14 | 14 |
) |
15 | 15 |
|
16 | 16 |
func spawnTestRegistry(t *testing.T) *Registry { |
17 |
- authConfig := &auth.AuthConfig{} |
|
17 |
+ authConfig := &AuthConfig{} |
|
18 | 18 |
r, err := NewRegistry(authConfig, utils.NewHTTPRequestFactory(), makeURL("/v1/")) |
19 | 19 |
if err != nil { |
20 | 20 |
t.Fatal(err) |
... | ... |
@@ -137,7 +136,7 @@ func TestResolveRepositoryName(t *testing.T) { |
137 | 137 |
if err != nil { |
138 | 138 |
t.Fatal(err) |
139 | 139 |
} |
140 |
- assertEqual(t, ep, auth.IndexServerAddress(), "Expected endpoint to be index server address") |
|
140 |
+ assertEqual(t, ep, IndexServerAddress(), "Expected endpoint to be index server address") |
|
141 | 141 |
assertEqual(t, repo, "fooo/bar", "Expected resolved repo to be foo/bar") |
142 | 142 |
|
143 | 143 |
u := makeURL("")[7:] |
... | ... |
@@ -4,7 +4,6 @@ import ( |
4 | 4 |
"encoding/json" |
5 | 5 |
"fmt" |
6 | 6 |
"github.com/dotcloud/docker/archive" |
7 |
- "github.com/dotcloud/docker/auth" |
|
8 | 7 |
"github.com/dotcloud/docker/daemonconfig" |
9 | 8 |
"github.com/dotcloud/docker/dockerversion" |
10 | 9 |
"github.com/dotcloud/docker/engine" |
... | ... |
@@ -199,19 +198,19 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { |
199 | 199 |
func (srv *Server) Auth(job *engine.Job) engine.Status { |
200 | 200 |
var ( |
201 | 201 |
err error |
202 |
- authConfig = &auth.AuthConfig{} |
|
202 |
+ authConfig = ®istry.AuthConfig{} |
|
203 | 203 |
) |
204 | 204 |
|
205 | 205 |
job.GetenvJson("authConfig", authConfig) |
206 | 206 |
// TODO: this is only done here because auth and registry need to be merged into one pkg |
207 |
- if addr := authConfig.ServerAddress; addr != "" && addr != auth.IndexServerAddress() { |
|
207 |
+ if addr := authConfig.ServerAddress; addr != "" && addr != registry.IndexServerAddress() { |
|
208 | 208 |
addr, err = registry.ExpandAndVerifyRegistryUrl(addr) |
209 | 209 |
if err != nil { |
210 | 210 |
return job.Error(err) |
211 | 211 |
} |
212 | 212 |
authConfig.ServerAddress = addr |
213 | 213 |
} |
214 |
- status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) |
|
214 |
+ status, err := registry.Login(authConfig, srv.HTTPRequestFactory(nil)) |
|
215 | 215 |
if err != nil { |
216 | 216 |
return job.Error(err) |
217 | 217 |
} |
... | ... |
@@ -431,8 +430,8 @@ func (srv *Server) Build(job *engine.Job) engine.Status { |
431 | 431 |
suppressOutput = job.GetenvBool("q") |
432 | 432 |
noCache = job.GetenvBool("nocache") |
433 | 433 |
rm = job.GetenvBool("rm") |
434 |
- authConfig = &auth.AuthConfig{} |
|
435 |
- configFile = &auth.ConfigFile{} |
|
434 |
+ authConfig = ®istry.AuthConfig{} |
|
435 |
+ configFile = ®istry.ConfigFile{} |
|
436 | 436 |
tag string |
437 | 437 |
context io.ReadCloser |
438 | 438 |
) |
... | ... |
@@ -611,12 +610,12 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { |
611 | 611 |
var ( |
612 | 612 |
term = job.Args[0] |
613 | 613 |
metaHeaders = map[string][]string{} |
614 |
- authConfig = &auth.AuthConfig{} |
|
614 |
+ authConfig = ®istry.AuthConfig{} |
|
615 | 615 |
) |
616 | 616 |
job.GetenvJson("authConfig", authConfig) |
617 | 617 |
job.GetenvJson("metaHeaders", metaHeaders) |
618 | 618 |
|
619 |
- r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) |
|
619 |
+ r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), registry.IndexServerAddress()) |
|
620 | 620 |
if err != nil { |
621 | 621 |
return job.Error(err) |
622 | 622 |
} |
... | ... |
@@ -827,7 +826,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { |
827 | 827 |
v.Set("ExecutionDriver", srv.runtime.ExecutionDriver().Name()) |
828 | 828 |
v.SetInt("NEventsListener", len(srv.listeners)) |
829 | 829 |
v.Set("KernelVersion", kernelVersion) |
830 |
- v.Set("IndexServerAddress", auth.IndexServerAddress()) |
|
830 |
+ v.Set("IndexServerAddress", registry.IndexServerAddress()) |
|
831 | 831 |
v.Set("InitSha1", dockerversion.INITSHA1) |
832 | 832 |
v.Set("InitPath", initPath) |
833 | 833 |
if _, err := v.WriteTo(job.Stdout); err != nil { |
... | ... |
@@ -1312,7 +1311,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { |
1312 | 1312 |
localName = job.Args[0] |
1313 | 1313 |
tag string |
1314 | 1314 |
sf = utils.NewStreamFormatter(job.GetenvBool("json")) |
1315 |
- authConfig = &auth.AuthConfig{} |
|
1315 |
+ authConfig = ®istry.AuthConfig{} |
|
1316 | 1316 |
metaHeaders map[string][]string |
1317 | 1317 |
) |
1318 | 1318 |
if len(job.Args) > 1 { |
... | ... |
@@ -1350,7 +1349,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { |
1350 | 1350 |
return job.Error(err) |
1351 | 1351 |
} |
1352 | 1352 |
|
1353 |
- if endpoint == auth.IndexServerAddress() { |
|
1353 |
+ if endpoint == registry.IndexServerAddress() { |
|
1354 | 1354 |
// If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" |
1355 | 1355 |
localName = remoteName |
1356 | 1356 |
} |
... | ... |
@@ -1531,7 +1530,7 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { |
1531 | 1531 |
var ( |
1532 | 1532 |
localName = job.Args[0] |
1533 | 1533 |
sf = utils.NewStreamFormatter(job.GetenvBool("json")) |
1534 |
- authConfig = &auth.AuthConfig{} |
|
1534 |
+ authConfig = ®istry.AuthConfig{} |
|
1535 | 1535 |
metaHeaders map[string][]string |
1536 | 1536 |
) |
1537 | 1537 |
|