mirror of https://github.com/docker/docs.git
				
				
				
			Merge branch '1357-implement-login-with-private-registry' of git://github.com/mhennings/docker into mhennings-1357-implement-login-with-private-registry
This commit is contained in:
		
						commit
						34edbd4f7e
					
				
							
								
								
									
										32
									
								
								api.go
								
								
								
								
							
							
						
						
									
										32
									
								
								api.go
								
								
								
								
							|  | @ -2,6 +2,7 @@ package docker | |||
| 
 | ||||
| import ( | ||||
| 	"code.google.com/p/go.net/websocket" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"github.com/dotcloud/docker/auth" | ||||
|  | @ -394,6 +395,16 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht | |||
| 	tag := r.Form.Get("tag") | ||||
| 	repo := r.Form.Get("repo") | ||||
| 
 | ||||
| 	authEncoded := r.Header.Get("X-Registry-Auth") | ||||
| 	authConfig := &auth.AuthConfig{} | ||||
| 	if authEncoded != "" { | ||||
| 		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) | ||||
| 		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { | ||||
| 			// for a pull it is not an error if no auth was given
 | ||||
| 			// to increase compatibility with the existing api it is defaulting to be empty
 | ||||
| 			authConfig = &auth.AuthConfig{} | ||||
| 		} | ||||
| 	} | ||||
| 	if version > 1.0 { | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 	} | ||||
|  | @ -405,7 +416,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht | |||
| 				metaHeaders[k] = v | ||||
| 			} | ||||
| 		} | ||||
| 		if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}, metaHeaders, version > 1.3); err != nil { | ||||
| 		if err := srv.ImagePull(image, tag, w, sf, authConfig, metaHeaders, version > 1.3); err != nil { | ||||
| 			if sf.Used() { | ||||
| 				w.Write(sf.FormatError(err)) | ||||
| 				return nil | ||||
|  | @ -473,18 +484,31 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht | |||
| } | ||||
| 
 | ||||
| func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { | ||||
| 	authConfig := &auth.AuthConfig{} | ||||
| 	metaHeaders := map[string][]string{} | ||||
| 	for k, v := range r.Header { | ||||
| 		if strings.HasPrefix(k, "X-Meta-") { | ||||
| 			metaHeaders[k] = v | ||||
| 		} | ||||
| 	} | ||||
| 	if err := parseForm(r); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	authConfig := &auth.AuthConfig{} | ||||
| 
 | ||||
| 	authEncoded := r.Header.Get("X-Registry-Auth") | ||||
| 	if authEncoded != "" { | ||||
| 		// the new format is to handle the authConfig as a header
 | ||||
| 		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) | ||||
| 		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { | ||||
| 			// to increase compatibility to existing api it is defaulting to be empty
 | ||||
| 			authConfig = &auth.AuthConfig{} | ||||
| 		} | ||||
| 	} else { | ||||
| 		// the old format is supported for compatibility if there was no authConfig header
 | ||||
| 		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	if err := parseForm(r); err != nil { | ||||
| 		return err | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	if vars == nil { | ||||
|  |  | |||
							
								
								
									
										81
									
								
								auth/auth.go
								
								
								
								
							
							
						
						
									
										81
									
								
								auth/auth.go
								
								
								
								
							|  | @ -30,6 +30,7 @@ type AuthConfig struct { | |||
| 	Password      string `json:"password,omitempty"` | ||||
| 	Auth          string `json:"auth"` | ||||
| 	Email         string `json:"email"` | ||||
| 	ServerAddress string `json:"serveraddress,omitempty"` | ||||
| } | ||||
| 
 | ||||
| type ConfigFile struct { | ||||
|  | @ -96,6 +97,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { | |||
| 		} | ||||
| 		origEmail := strings.Split(arr[1], " = ") | ||||
| 		authConfig.Email = origEmail[1] | ||||
| 		authConfig.ServerAddress = IndexServerAddress() | ||||
| 		configFile.Configs[IndexServerAddress()] = authConfig | ||||
| 	} else { | ||||
| 		for k, authConfig := range configFile.Configs { | ||||
|  | @ -105,6 +107,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { | |||
| 			} | ||||
| 			authConfig.Auth = "" | ||||
| 			configFile.Configs[k] = authConfig | ||||
| 			authConfig.ServerAddress = k | ||||
| 		} | ||||
| 	} | ||||
| 	return &configFile, nil | ||||
|  | @ -125,7 +128,7 @@ func SaveConfig(configFile *ConfigFile) error { | |||
| 		authCopy.Auth = encodeAuth(&authCopy) | ||||
| 		authCopy.Username = "" | ||||
| 		authCopy.Password = "" | ||||
| 
 | ||||
| 		authCopy.ServerAddress = "" | ||||
| 		configs[k] = authCopy | ||||
| 	} | ||||
| 
 | ||||
|  | @ -146,14 +149,26 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | |||
| 	reqStatusCode := 0 | ||||
| 	var status string | ||||
| 	var reqBody []byte | ||||
| 	jsonBody, err := json.Marshal(authConfig) | ||||
| 
 | ||||
| 	serverAddress := authConfig.ServerAddress | ||||
| 	if serverAddress == "" { | ||||
| 		serverAddress = IndexServerAddress() | ||||
| 	} | ||||
| 
 | ||||
| 	loginAgainstOfficialIndex := serverAddress == IndexServerAddress() | ||||
| 
 | ||||
| 	// to avoid sending the server address to the server it should be removed before marshalled
 | ||||
| 	authCopy := *authConfig | ||||
| 	authCopy.ServerAddress = "" | ||||
| 
 | ||||
| 	jsonBody, err := json.Marshal(authCopy) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("Config Error: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 | ||||
| 	b := strings.NewReader(string(jsonBody)) | ||||
| 	req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b) | ||||
| 	req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("Server Error: %s", err) | ||||
| 	} | ||||
|  | @ -165,14 +180,23 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | |||
| 	} | ||||
| 
 | ||||
| 	if reqStatusCode == 201 { | ||||
| 		if loginAgainstOfficialIndex { | ||||
| 			status = "Account created. Please use the confirmation link we sent" + | ||||
| 				" to your e-mail to activate it." | ||||
| 		} else { | ||||
| 			status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." | ||||
| 		} | ||||
| 	} else if reqStatusCode == 403 { | ||||
| 		if loginAgainstOfficialIndex { | ||||
| 			return "", fmt.Errorf("Login: Your account hasn't been activated. " + | ||||
| 				"Please check your e-mail for a confirmation link.") | ||||
| 		} else { | ||||
| 			return "", fmt.Errorf("Login: Your account hasn't been activated. " + | ||||
| 				"Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.") | ||||
| 		} | ||||
| 	} else if reqStatusCode == 400 { | ||||
| 		if string(reqBody) == "\"Username or email already exists\"" { | ||||
| 			req, err := factory.NewRequest("GET", IndexServerAddress()+"users/", nil) | ||||
| 			req, err := factory.NewRequest("GET", serverAddress+"users/", nil) | ||||
| 			req.SetBasicAuth(authConfig.Username, authConfig.Password) | ||||
| 			resp, err := client.Do(req) | ||||
| 			if err != nil { | ||||
|  | @ -199,3 +223,52 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | |||
| 	} | ||||
| 	return status, nil | ||||
| } | ||||
| 
 | ||||
| // this method matches a auth configuration to a server address or a url
 | ||||
| func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig { | ||||
| 	if registry == IndexServerAddress() || len(registry) == 0 { | ||||
| 		// default to the index server
 | ||||
| 		return config.Configs[IndexServerAddress()] | ||||
| 	} | ||||
| 	// if its not the index server there are three cases:
 | ||||
| 	//
 | ||||
| 	// 1. this is a full config url -> it should be used as is
 | ||||
| 	// 2. it could be a full url, but with the wrong protocol
 | ||||
| 	// 3. it can be the hostname optionally with a port
 | ||||
| 	//
 | ||||
| 	// as there is only one auth entry which is fully qualified we need to start
 | ||||
| 	// parsing and matching
 | ||||
| 
 | ||||
| 	swapProtocoll := func(url string) string { | ||||
| 		if strings.HasPrefix(url, "http:") { | ||||
| 			return strings.Replace(url, "http:", "https:", 1) | ||||
| 		} | ||||
| 		if strings.HasPrefix(url, "https:") { | ||||
| 			return strings.Replace(url, "https:", "http:", 1) | ||||
| 		} | ||||
| 		return url | ||||
| 	} | ||||
| 
 | ||||
| 	resolveIgnoringProtocol := func(url string) AuthConfig { | ||||
| 		if c, found := config.Configs[url]; found { | ||||
| 			return c | ||||
| 		} | ||||
| 		registrySwappedProtocoll := swapProtocoll(url) | ||||
| 		// now try to match with the different protocol
 | ||||
| 		if c, found := config.Configs[registrySwappedProtocoll]; found { | ||||
| 			return c | ||||
| 		} | ||||
| 		return AuthConfig{} | ||||
| 	} | ||||
| 
 | ||||
| 	// match both protocols as it could also be a server name like httpfoo
 | ||||
| 	if strings.HasPrefix(registry, "http:") || strings.HasPrefix(registry, "https:") { | ||||
| 		return resolveIgnoringProtocol(registry) | ||||
| 	} | ||||
| 
 | ||||
| 	url := "https://" + registry | ||||
| 	if !strings.Contains(registry, "/") { | ||||
| 		url = url + "/v1/" | ||||
| 	} | ||||
| 	return resolveIgnoringProtocol(url) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										135
									
								
								commands.go
								
								
								
								
							
							
						
						
									
										135
									
								
								commands.go
								
								
								
								
							|  | @ -4,10 +4,12 @@ import ( | |||
| 	"archive/tar" | ||||
| 	"bufio" | ||||
| 	"bytes" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"github.com/dotcloud/docker/auth" | ||||
| 	"github.com/dotcloud/docker/registry" | ||||
| 	"github.com/dotcloud/docker/term" | ||||
| 	"github.com/dotcloud/docker/utils" | ||||
| 	"io" | ||||
|  | @ -91,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { | |||
| 		{"login", "Register or Login to the docker registry server"}, | ||||
| 		{"logs", "Fetch the logs of a container"}, | ||||
| 		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, | ||||
| 		{"top", "Lookup the running processes of a container"}, | ||||
| 		{"ps", "List containers"}, | ||||
| 		{"pull", "Pull an image or a repository from the docker registry server"}, | ||||
| 		{"push", "Push an image or a repository to the docker registry server"}, | ||||
|  | @ -102,7 +105,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { | |||
| 		{"start", "Start a stopped container"}, | ||||
| 		{"stop", "Stop a running container"}, | ||||
| 		{"tag", "Tag an image into a repository"}, | ||||
| 		{"top", "Lookup the running processes of a container"}, | ||||
| 		{"version", "Show the docker version information"}, | ||||
| 		{"wait", "Block until a container stops, then print its exit code"}, | ||||
| 	} { | ||||
|  | @ -126,7 +128,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { | |||
| 	v.Set("url", cmd.Arg(1)) | ||||
| 	v.Set("path", cmd.Arg(2)) | ||||
| 
 | ||||
| 	if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out); err != nil { | ||||
| 	if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
|  | @ -187,10 +189,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { | |||
| 	} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { | ||||
| 		isRemote = true | ||||
| 	} else { | ||||
| 		if fi, err := os.Stat(cmd.Arg(0)); err != nil { | ||||
| 		if _, err := os.Stat(cmd.Arg(0)); err != nil { | ||||
| 			return err | ||||
| 		} else if !fi.IsDir() { | ||||
| 			return fmt.Errorf("\"%s\" is not a path or URL. Please provide a path to a directory containing a Dockerfile.", cmd.Arg(0)) | ||||
| 		} | ||||
| 		context, err = Tar(cmd.Arg(0), Uncompressed) | ||||
| 	} | ||||
|  | @ -254,7 +254,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { | |||
| 
 | ||||
| // 'docker login': login / register a user to registry service.
 | ||||
| func (cli *DockerCli) CmdLogin(args ...string) error { | ||||
| 	cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") | ||||
| 	cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.") | ||||
| 
 | ||||
| 	var username, password, email string | ||||
| 
 | ||||
|  | @ -262,10 +262,17 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | |||
| 	cmd.StringVar(&password, "p", "", "password") | ||||
| 	cmd.StringVar(&email, "e", "", "email") | ||||
| 	err := cmd.Parse(args) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	serverAddress := auth.IndexServerAddress() | ||||
| 	if len(cmd.Args()) > 0 { | ||||
| 		serverAddress, err = registry.ExpandAndVerifyRegistryUrl(cmd.Arg(0)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintf(cli.out, "Login against server at %s\n", serverAddress) | ||||
| 	} | ||||
| 
 | ||||
| 	promptDefault := func(prompt string, configDefault string) { | ||||
| 		if configDefault == "" { | ||||
|  | @ -298,19 +305,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | |||
| 			username = authconfig.Username | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if username != authconfig.Username { | ||||
| 		if password == "" { | ||||
| 			oldState, _ := term.SaveState(cli.terminalFd) | ||||
| 			fmt.Fprintf(cli.out, "Password: ") | ||||
| 
 | ||||
| 			term.DisableEcho(cli.terminalFd, oldState) | ||||
| 
 | ||||
| 			password = readInput(cli.in, cli.out) | ||||
| 			fmt.Fprint(cli.out, "\n") | ||||
| 
 | ||||
| 			term.RestoreTerminal(cli.terminalFd, oldState) | ||||
| 
 | ||||
| 			if password == "" { | ||||
| 				return fmt.Errorf("Error : Password Required") | ||||
| 			} | ||||
|  | @ -327,15 +331,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | |||
| 		password = authconfig.Password | ||||
| 		email = authconfig.Email | ||||
| 	} | ||||
| 
 | ||||
| 	authconfig.Username = username | ||||
| 	authconfig.Password = password | ||||
| 	authconfig.Email = email | ||||
| 	cli.configFile.Configs[auth.IndexServerAddress()] = authconfig | ||||
| 	authconfig.ServerAddress = serverAddress | ||||
| 	cli.configFile.Configs[serverAddress] = authconfig | ||||
| 
 | ||||
| 	body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()]) | ||||
| 	body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress]) | ||||
| 	if statusCode == 401 { | ||||
| 		delete(cli.configFile.Configs, auth.IndexServerAddress()) | ||||
| 		delete(cli.configFile.Configs, serverAddress) | ||||
| 		auth.SaveConfig(cli.configFile) | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -791,7 +795,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { | |||
| 	v.Set("tag", tag) | ||||
| 	v.Set("fromSrc", src) | ||||
| 
 | ||||
| 	err := cli.stream("POST", "/images/create?"+v.Encode(), cli.in, cli.out) | ||||
| 	err := cli.stream("POST", "/images/create?"+v.Encode(), cli.in, cli.out, nil) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -812,6 +816,13 @@ func (cli *DockerCli) CmdPush(args ...string) error { | |||
| 
 | ||||
| 	cli.LoadConfigFile() | ||||
| 
 | ||||
| 	// Resolve the Repository name from fqn to endpoint + name
 | ||||
| 	endpoint, _, err := registry.ResolveRepositoryName(name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Resolve the Auth config relevant for this server
 | ||||
| 	authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||
| 	// If we're not using a custom registry, we know the restrictions
 | ||||
| 	// applied to repository names and can warn the user in advance.
 | ||||
| 	// Custom repositories can have different rules, and we must also
 | ||||
|  | @ -825,22 +836,28 @@ func (cli *DockerCli) CmdPush(args ...string) error { | |||
| 	} | ||||
| 
 | ||||
| 	v := url.Values{} | ||||
| 	push := func() error { | ||||
| 		buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) | ||||
| 	push := func(authConfig auth.AuthConfig) error { | ||||
| 		buf, err := json.Marshal(authConfig) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out) | ||||
| 		registryAuthHeader := []string{ | ||||
| 			base64.URLEncoding.EncodeToString(buf), | ||||
| 		} | ||||
| 
 | ||||
| 	if err := push(); err != nil { | ||||
| 		if err.Error() == "Authentication is required." { | ||||
| 		return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ | ||||
| 			"X-Registry-Auth": registryAuthHeader, | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := push(authConfig); err != nil { | ||||
| 		if err.Error() == registry.ErrLoginRequired.Error() { | ||||
| 			fmt.Fprintln(cli.out, "\nPlease login prior to push:") | ||||
| 			if err := cli.CmdLogin(""); err != nil { | ||||
| 			if err := cli.CmdLogin(endpoint); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			return push() | ||||
| 			authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||
| 			return push(authConfig) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -864,11 +881,43 @@ func (cli *DockerCli) CmdPull(args ...string) error { | |||
| 		*tag = parsedTag | ||||
| 	} | ||||
| 
 | ||||
| 	// Resolve the Repository name from fqn to endpoint + name
 | ||||
| 	endpoint, _, err := registry.ResolveRepositoryName(remote) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	cli.LoadConfigFile() | ||||
| 
 | ||||
| 	// Resolve the Auth config relevant for this server
 | ||||
| 	authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||
| 	v := url.Values{} | ||||
| 	v.Set("fromImage", remote) | ||||
| 	v.Set("tag", *tag) | ||||
| 
 | ||||
| 	if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil { | ||||
| 	pull := func(authConfig auth.AuthConfig) error { | ||||
| 		buf, err := json.Marshal(authConfig) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		registryAuthHeader := []string{ | ||||
| 			base64.URLEncoding.EncodeToString(buf), | ||||
| 		} | ||||
| 
 | ||||
| 		return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ | ||||
| 			"X-Registry-Auth": registryAuthHeader, | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := pull(authConfig); err != nil { | ||||
| 		if err.Error() == registry.ErrLoginRequired.Error() { | ||||
| 			fmt.Fprintln(cli.out, "\nPlease login prior to push:") | ||||
| 			if err := cli.CmdLogin(endpoint); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||
| 			return pull(authConfig) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
|  | @ -1102,7 +1151,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error { | |||
| 		v.Set("since", *since) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil { | ||||
| 	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
|  | @ -1119,7 +1168,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { | |||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out); err != nil { | ||||
| 	if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
|  | @ -1391,7 +1440,30 @@ func (cli *DockerCli) CmdRun(args ...string) error { | |||
| 		repos, tag := utils.ParseRepositoryTag(config.Image) | ||||
| 		v.Set("fromImage", repos) | ||||
| 		v.Set("tag", tag) | ||||
| 		err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err) | ||||
| 
 | ||||
| 		// Resolve the Repository name from fqn to endpoint + name
 | ||||
| 		var endpoint string | ||||
| 		endpoint, _, err = registry.ResolveRepositoryName(repos) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		// Load the auth config file, to be able to pull the image
 | ||||
| 		cli.LoadConfigFile() | ||||
| 
 | ||||
| 		// Resolve the Auth config relevant for this server
 | ||||
| 		authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||
| 		buf, err := json.Marshal(authConfig) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		registryAuthHeader := []string{ | ||||
| 			base64.URLEncoding.EncodeToString(buf), | ||||
| 		} | ||||
| 		err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{ | ||||
| 			"X-Registry-Auth": registryAuthHeader, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | @ -1568,7 +1640,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, | |||
| 	return body, resp.StatusCode, nil | ||||
| } | ||||
| 
 | ||||
| func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) error { | ||||
| func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { | ||||
| 	if (method == "POST" || method == "PUT") && in == nil { | ||||
| 		in = bytes.NewReader([]byte{}) | ||||
| 	} | ||||
|  | @ -1581,6 +1653,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e | |||
| 	if method == "POST" { | ||||
| 		req.Header.Set("Content-Type", "plain/text") | ||||
| 	} | ||||
| 
 | ||||
| 	if headers != nil { | ||||
| 		for k, v := range headers { | ||||
| 			req.Header[k] = v | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	dial, err := net.Dial(cli.proto, cli.addr) | ||||
| 	if err != nil { | ||||
| 		if strings.Contains(err.Error(), "connection refused") { | ||||
|  |  | |||
|  | @ -991,7 +991,8 @@ Check auth configuration | |||
| 	   { | ||||
| 		"username":"hannibal", | ||||
| 		"password:"xxxx", | ||||
| 		"email":"hannibal@a-team.com" | ||||
| 		"email":"hannibal@a-team.com", | ||||
| 		"serveraddress":"https://index.docker.io/v1/" | ||||
| 	   } | ||||
| 
 | ||||
|         **Example response**: | ||||
|  |  | |||
|  | @ -8,10 +8,17 @@ | |||
| 
 | ||||
| :: | ||||
| 
 | ||||
|     Usage: docker login [OPTIONS] | ||||
|     Usage: docker login [OPTIONS] [SERVER] | ||||
| 
 | ||||
|     Register or Login to the docker registry server | ||||
| 
 | ||||
|     -e="": email | ||||
|     -p="": password | ||||
|     -u="": username | ||||
| 
 | ||||
|     If you want to login to a private registry you can | ||||
|     specify this by adding the server name. | ||||
| 
 | ||||
|     example: | ||||
|     docker login localhost:8080 | ||||
| 
 | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import ( | |||
| var ( | ||||
| 	ErrAlreadyExists         = errors.New("Image already exists") | ||||
| 	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") | ||||
| 	ErrLoginRequired         = errors.New("Authentication is required.") | ||||
| ) | ||||
| 
 | ||||
| func pingRegistryEndpoint(endpoint string) error { | ||||
|  | @ -102,17 +103,38 @@ func ResolveRepositoryName(reposName string) (string, string, error) { | |||
| 	if err := validateRepositoryName(reposName); err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	endpoint, err := ExpandAndVerifyRegistryUrl(hostname) | ||||
| 	if err != nil { | ||||
| 		return "", "", err | ||||
| 	} | ||||
| 	return endpoint, reposName, err | ||||
| } | ||||
| 
 | ||||
| // this method expands the registry name as used in the prefix of a repo
 | ||||
| // to a full url. if it already is a url, there will be no change.
 | ||||
| // The registry is pinged to test if it http or https
 | ||||
| func ExpandAndVerifyRegistryUrl(hostname string) (string, error) { | ||||
| 	if strings.HasPrefix(hostname, "http:") || strings.HasPrefix(hostname, "https:") { | ||||
| 		// if there is no slash after https:// (8 characters) then we have no path in the url
 | ||||
| 		if strings.LastIndex(hostname, "/") < 9 { | ||||
| 			// there is no path given. Expand with default path
 | ||||
| 			hostname = hostname + "/v1/" | ||||
| 		} | ||||
| 		if err := pingRegistryEndpoint(hostname); err != nil { | ||||
| 			return "", errors.New("Invalid Registry endpoint: " + err.Error()) | ||||
| 		} | ||||
| 		return hostname, nil | ||||
| 	} | ||||
| 	endpoint := fmt.Sprintf("https://%s/v1/", hostname) | ||||
| 	if err := pingRegistryEndpoint(endpoint); err != nil { | ||||
| 		utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) | ||||
| 		endpoint = fmt.Sprintf("http://%s/v1/", hostname) | ||||
| 		if err = pingRegistryEndpoint(endpoint); err != nil { | ||||
| 			//TODO: triggering highland build can be done there without "failing"
 | ||||
| 			return "", "", errors.New("Invalid Registry endpoint: " + err.Error()) | ||||
| 			return "", errors.New("Invalid Registry endpoint: " + err.Error()) | ||||
| 		} | ||||
| 	} | ||||
| 	err := validateRepositoryName(reposName) | ||||
| 	return endpoint, reposName, err | ||||
| 	return endpoint, nil | ||||
| } | ||||
| 
 | ||||
| func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { | ||||
|  | @ -139,6 +161,9 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s | |||
| 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) | ||||
| 	res, err := doWithCookies(r.client, req) | ||||
| 	if err != nil || res.StatusCode != 200 { | ||||
| 		if res.StatusCode == 401 { | ||||
| 			return nil, ErrLoginRequired | ||||
| 		} | ||||
| 		if res != nil { | ||||
| 			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) | ||||
| 		} | ||||
|  | @ -282,7 +307,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e | |||
| 	} | ||||
| 	defer res.Body.Close() | ||||
| 	if res.StatusCode == 401 { | ||||
| 		return nil, utils.NewHTTPRequestError(fmt.Sprintf("Please login first (HTTP code %d)", res.StatusCode), res) | ||||
| 		return nil, ErrLoginRequired | ||||
| 	} | ||||
| 	// TODO: Right now we're ignoring checksums in the response body.
 | ||||
| 	// In the future, we need to use them to check image validity.
 | ||||
|  |  | |||
|  | @ -655,6 +655,9 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut | |||
| 
 | ||||
| 	out = utils.NewWriteFlusher(out) | ||||
| 	err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel) | ||||
| 	if err == registry.ErrLoginRequired { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { | ||||
| 			return err | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue