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 ( | import ( | ||||||
| 	"code.google.com/p/go.net/websocket" | 	"code.google.com/p/go.net/websocket" | ||||||
|  | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/dotcloud/docker/auth" | 	"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") | 	tag := r.Form.Get("tag") | ||||||
| 	repo := r.Form.Get("repo") | 	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 { | 	if version > 1.0 { | ||||||
| 		w.Header().Set("Content-Type", "application/json") | 		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 | 				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() { | 			if sf.Used() { | ||||||
| 				w.Write(sf.FormatError(err)) | 				w.Write(sf.FormatError(err)) | ||||||
| 				return nil | 				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 { | func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { | ||||||
| 	authConfig := &auth.AuthConfig{} |  | ||||||
| 	metaHeaders := map[string][]string{} | 	metaHeaders := map[string][]string{} | ||||||
| 	for k, v := range r.Header { | 	for k, v := range r.Header { | ||||||
| 		if strings.HasPrefix(k, "X-Meta-") { | 		if strings.HasPrefix(k, "X-Meta-") { | ||||||
| 			metaHeaders[k] = v | 			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 { | 		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	if err := parseForm(r); err != nil { | 
 | ||||||
| 		return err |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if vars == nil { | 	if vars == nil { | ||||||
|  |  | ||||||
							
								
								
									
										81
									
								
								auth/auth.go
								
								
								
								
							
							
						
						
									
										81
									
								
								auth/auth.go
								
								
								
								
							|  | @ -30,6 +30,7 @@ type AuthConfig struct { | ||||||
| 	Password      string `json:"password,omitempty"` | 	Password      string `json:"password,omitempty"` | ||||||
| 	Auth          string `json:"auth"` | 	Auth          string `json:"auth"` | ||||||
| 	Email         string `json:"email"` | 	Email         string `json:"email"` | ||||||
|  | 	ServerAddress string `json:"serveraddress,omitempty"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ConfigFile struct { | type ConfigFile struct { | ||||||
|  | @ -96,6 +97,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { | ||||||
| 		} | 		} | ||||||
| 		origEmail := strings.Split(arr[1], " = ") | 		origEmail := strings.Split(arr[1], " = ") | ||||||
| 		authConfig.Email = origEmail[1] | 		authConfig.Email = origEmail[1] | ||||||
|  | 		authConfig.ServerAddress = IndexServerAddress() | ||||||
| 		configFile.Configs[IndexServerAddress()] = authConfig | 		configFile.Configs[IndexServerAddress()] = authConfig | ||||||
| 	} else { | 	} else { | ||||||
| 		for k, authConfig := range configFile.Configs { | 		for k, authConfig := range configFile.Configs { | ||||||
|  | @ -105,6 +107,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { | ||||||
| 			} | 			} | ||||||
| 			authConfig.Auth = "" | 			authConfig.Auth = "" | ||||||
| 			configFile.Configs[k] = authConfig | 			configFile.Configs[k] = authConfig | ||||||
|  | 			authConfig.ServerAddress = k | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return &configFile, nil | 	return &configFile, nil | ||||||
|  | @ -125,7 +128,7 @@ func SaveConfig(configFile *ConfigFile) error { | ||||||
| 		authCopy.Auth = encodeAuth(&authCopy) | 		authCopy.Auth = encodeAuth(&authCopy) | ||||||
| 		authCopy.Username = "" | 		authCopy.Username = "" | ||||||
| 		authCopy.Password = "" | 		authCopy.Password = "" | ||||||
| 
 | 		authCopy.ServerAddress = "" | ||||||
| 		configs[k] = authCopy | 		configs[k] = authCopy | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -146,14 +149,26 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | ||||||
| 	reqStatusCode := 0 | 	reqStatusCode := 0 | ||||||
| 	var status string | 	var status string | ||||||
| 	var reqBody []byte | 	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 { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("Config Error: %s", err) | 		return "", fmt.Errorf("Config Error: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 | 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 | ||||||
| 	b := strings.NewReader(string(jsonBody)) | 	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 { | 	if err != nil { | ||||||
| 		return "", fmt.Errorf("Server Error: %s", err) | 		return "", fmt.Errorf("Server Error: %s", err) | ||||||
| 	} | 	} | ||||||
|  | @ -165,14 +180,23 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if reqStatusCode == 201 { | 	if reqStatusCode == 201 { | ||||||
|  | 		if loginAgainstOfficialIndex { | ||||||
| 			status = "Account created. Please use the confirmation link we sent" + | 			status = "Account created. Please use the confirmation link we sent" + | ||||||
| 				" to your e-mail to activate it." | 				" 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 { | 	} else if reqStatusCode == 403 { | ||||||
|  | 		if loginAgainstOfficialIndex { | ||||||
| 			return "", fmt.Errorf("Login: Your account hasn't been activated. " + | 			return "", fmt.Errorf("Login: Your account hasn't been activated. " + | ||||||
| 				"Please check your e-mail for a confirmation link.") | 				"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 { | 	} else if reqStatusCode == 400 { | ||||||
| 		if string(reqBody) == "\"Username or email already exists\"" { | 		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) | 			req.SetBasicAuth(authConfig.Username, authConfig.Password) | ||||||
| 			resp, err := client.Do(req) | 			resp, err := client.Do(req) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
|  | @ -199,3 +223,52 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e | ||||||
| 	} | 	} | ||||||
| 	return status, nil | 	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" | 	"archive/tar" | ||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"encoding/base64" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"github.com/dotcloud/docker/auth" | 	"github.com/dotcloud/docker/auth" | ||||||
|  | 	"github.com/dotcloud/docker/registry" | ||||||
| 	"github.com/dotcloud/docker/term" | 	"github.com/dotcloud/docker/term" | ||||||
| 	"github.com/dotcloud/docker/utils" | 	"github.com/dotcloud/docker/utils" | ||||||
| 	"io" | 	"io" | ||||||
|  | @ -91,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { | ||||||
| 		{"login", "Register or Login to the docker registry server"}, | 		{"login", "Register or Login to the docker registry server"}, | ||||||
| 		{"logs", "Fetch the logs of a container"}, | 		{"logs", "Fetch the logs of a container"}, | ||||||
| 		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, | 		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, | ||||||
|  | 		{"top", "Lookup the running processes of a container"}, | ||||||
| 		{"ps", "List containers"}, | 		{"ps", "List containers"}, | ||||||
| 		{"pull", "Pull an image or a repository from the docker registry server"}, | 		{"pull", "Pull an image or a repository from the docker registry server"}, | ||||||
| 		{"push", "Push an image or a repository to 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"}, | 		{"start", "Start a stopped container"}, | ||||||
| 		{"stop", "Stop a running container"}, | 		{"stop", "Stop a running container"}, | ||||||
| 		{"tag", "Tag an image into a repository"}, | 		{"tag", "Tag an image into a repository"}, | ||||||
| 		{"top", "Lookup the running processes of a container"}, |  | ||||||
| 		{"version", "Show the docker version information"}, | 		{"version", "Show the docker version information"}, | ||||||
| 		{"wait", "Block until a container stops, then print its exit code"}, | 		{"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("url", cmd.Arg(1)) | ||||||
| 	v.Set("path", cmd.Arg(2)) | 	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 err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	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)) { | 	} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { | ||||||
| 		isRemote = true | 		isRemote = true | ||||||
| 	} else { | 	} else { | ||||||
| 		if fi, err := os.Stat(cmd.Arg(0)); err != nil { | 		if _, err := os.Stat(cmd.Arg(0)); err != nil { | ||||||
| 			return err | 			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) | 		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.
 | // 'docker login': login / register a user to registry service.
 | ||||||
| func (cli *DockerCli) CmdLogin(args ...string) error { | 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 | 	var username, password, email string | ||||||
| 
 | 
 | ||||||
|  | @ -262,10 +262,17 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | ||||||
| 	cmd.StringVar(&password, "p", "", "password") | 	cmd.StringVar(&password, "p", "", "password") | ||||||
| 	cmd.StringVar(&email, "e", "", "email") | 	cmd.StringVar(&email, "e", "", "email") | ||||||
| 	err := cmd.Parse(args) | 	err := cmd.Parse(args) | ||||||
| 
 |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return 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) { | 	promptDefault := func(prompt string, configDefault string) { | ||||||
| 		if configDefault == "" { | 		if configDefault == "" { | ||||||
|  | @ -298,19 +305,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | ||||||
| 			username = authconfig.Username | 			username = authconfig.Username | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	if username != authconfig.Username { | 	if username != authconfig.Username { | ||||||
| 		if password == "" { | 		if password == "" { | ||||||
| 			oldState, _ := term.SaveState(cli.terminalFd) | 			oldState, _ := term.SaveState(cli.terminalFd) | ||||||
| 			fmt.Fprintf(cli.out, "Password: ") | 			fmt.Fprintf(cli.out, "Password: ") | ||||||
| 
 |  | ||||||
| 			term.DisableEcho(cli.terminalFd, oldState) | 			term.DisableEcho(cli.terminalFd, oldState) | ||||||
| 
 | 
 | ||||||
| 			password = readInput(cli.in, cli.out) | 			password = readInput(cli.in, cli.out) | ||||||
| 			fmt.Fprint(cli.out, "\n") | 			fmt.Fprint(cli.out, "\n") | ||||||
| 
 | 
 | ||||||
| 			term.RestoreTerminal(cli.terminalFd, oldState) | 			term.RestoreTerminal(cli.terminalFd, oldState) | ||||||
| 
 |  | ||||||
| 			if password == "" { | 			if password == "" { | ||||||
| 				return fmt.Errorf("Error : Password Required") | 				return fmt.Errorf("Error : Password Required") | ||||||
| 			} | 			} | ||||||
|  | @ -327,15 +331,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { | ||||||
| 		password = authconfig.Password | 		password = authconfig.Password | ||||||
| 		email = authconfig.Email | 		email = authconfig.Email | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	authconfig.Username = username | 	authconfig.Username = username | ||||||
| 	authconfig.Password = password | 	authconfig.Password = password | ||||||
| 	authconfig.Email = email | 	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 { | 	if statusCode == 401 { | ||||||
| 		delete(cli.configFile.Configs, auth.IndexServerAddress()) | 		delete(cli.configFile.Configs, serverAddress) | ||||||
| 		auth.SaveConfig(cli.configFile) | 		auth.SaveConfig(cli.configFile) | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -791,7 +795,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { | ||||||
| 	v.Set("tag", tag) | 	v.Set("tag", tag) | ||||||
| 	v.Set("fromSrc", src) | 	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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -812,6 +816,13 @@ func (cli *DockerCli) CmdPush(args ...string) error { | ||||||
| 
 | 
 | ||||||
| 	cli.LoadConfigFile() | 	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
 | 	// If we're not using a custom registry, we know the restrictions
 | ||||||
| 	// applied to repository names and can warn the user in advance.
 | 	// applied to repository names and can warn the user in advance.
 | ||||||
| 	// Custom repositories can have different rules, and we must also
 | 	// Custom repositories can have different rules, and we must also
 | ||||||
|  | @ -825,22 +836,28 @@ func (cli *DockerCli) CmdPush(args ...string) error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	v := url.Values{} | 	v := url.Values{} | ||||||
| 	push := func() error { | 	push := func(authConfig auth.AuthConfig) error { | ||||||
| 		buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) | 		buf, err := json.Marshal(authConfig) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 		registryAuthHeader := []string{ | ||||||
| 		return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out) | 			base64.URLEncoding.EncodeToString(buf), | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	if err := push(); err != nil { | 		return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ | ||||||
| 		if err.Error() == "Authentication is required." { | 			"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:") | 			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 err | ||||||
| 			} | 			} | ||||||
| 			return push() | 			authConfig := cli.configFile.ResolveAuthConfig(endpoint) | ||||||
|  | 			return push(authConfig) | ||||||
| 		} | 		} | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -864,11 +881,43 @@ func (cli *DockerCli) CmdPull(args ...string) error { | ||||||
| 		*tag = parsedTag | 		*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 := url.Values{} | ||||||
| 	v.Set("fromImage", remote) | 	v.Set("fromImage", remote) | ||||||
| 	v.Set("tag", *tag) | 	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 | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -1102,7 +1151,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error { | ||||||
| 		v.Set("since", *since) | 		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 err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -1119,7 +1168,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { | ||||||
| 		return nil | 		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 err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|  | @ -1391,7 +1440,30 @@ func (cli *DockerCli) CmdRun(args ...string) error { | ||||||
| 		repos, tag := utils.ParseRepositoryTag(config.Image) | 		repos, tag := utils.ParseRepositoryTag(config.Image) | ||||||
| 		v.Set("fromImage", repos) | 		v.Set("fromImage", repos) | ||||||
| 		v.Set("tag", tag) | 		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 { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | @ -1568,7 +1640,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, | ||||||
| 	return body, resp.StatusCode, nil | 	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 { | 	if (method == "POST" || method == "PUT") && in == nil { | ||||||
| 		in = bytes.NewReader([]byte{}) | 		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" { | 	if method == "POST" { | ||||||
| 		req.Header.Set("Content-Type", "plain/text") | 		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) | 	dial, err := net.Dial(cli.proto, cli.addr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if strings.Contains(err.Error(), "connection refused") { | 		if strings.Contains(err.Error(), "connection refused") { | ||||||
|  |  | ||||||
|  | @ -991,7 +991,8 @@ Check auth configuration | ||||||
| 	   { | 	   { | ||||||
| 		"username":"hannibal", | 		"username":"hannibal", | ||||||
| 		"password:"xxxx", | 		"password:"xxxx", | ||||||
| 		"email":"hannibal@a-team.com" | 		"email":"hannibal@a-team.com", | ||||||
|  | 		"serveraddress":"https://index.docker.io/v1/" | ||||||
| 	   } | 	   } | ||||||
| 
 | 
 | ||||||
|         **Example response**: |         **Example response**: | ||||||
|  |  | ||||||
|  | @ -8,10 +8,17 @@ | ||||||
| 
 | 
 | ||||||
| :: | :: | ||||||
| 
 | 
 | ||||||
|     Usage: docker login [OPTIONS] |     Usage: docker login [OPTIONS] [SERVER] | ||||||
| 
 | 
 | ||||||
|     Register or Login to the docker registry server |     Register or Login to the docker registry server | ||||||
| 
 | 
 | ||||||
|     -e="": email |     -e="": email | ||||||
|     -p="": password |     -p="": password | ||||||
|     -u="": username |     -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 ( | var ( | ||||||
| 	ErrAlreadyExists         = errors.New("Image already exists") | 	ErrAlreadyExists         = errors.New("Image already exists") | ||||||
| 	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") | 	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") | ||||||
|  | 	ErrLoginRequired         = errors.New("Authentication is required.") | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func pingRegistryEndpoint(endpoint string) error { | func pingRegistryEndpoint(endpoint string) error { | ||||||
|  | @ -102,17 +103,38 @@ func ResolveRepositoryName(reposName string) (string, string, error) { | ||||||
| 	if err := validateRepositoryName(reposName); err != nil { | 	if err := validateRepositoryName(reposName); err != nil { | ||||||
| 		return "", "", err | 		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) | 	endpoint := fmt.Sprintf("https://%s/v1/", hostname) | ||||||
| 	if err := pingRegistryEndpoint(endpoint); err != nil { | 	if err := pingRegistryEndpoint(endpoint); err != nil { | ||||||
| 		utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) | 		utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) | ||||||
| 		endpoint = fmt.Sprintf("http://%s/v1/", hostname) | 		endpoint = fmt.Sprintf("http://%s/v1/", hostname) | ||||||
| 		if err = pingRegistryEndpoint(endpoint); err != nil { | 		if err = pingRegistryEndpoint(endpoint); err != nil { | ||||||
| 			//TODO: triggering highland build can be done there without "failing"
 | 			//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, nil | ||||||
| 	return endpoint, reposName, err |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { | 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, ", ")) | 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) | ||||||
| 	res, err := doWithCookies(r.client, req) | 	res, err := doWithCookies(r.client, req) | ||||||
| 	if err != nil || res.StatusCode != 200 { | 	if err != nil || res.StatusCode != 200 { | ||||||
|  | 		if res.StatusCode == 401 { | ||||||
|  | 			return nil, ErrLoginRequired | ||||||
|  | 		} | ||||||
| 		if res != nil { | 		if res != nil { | ||||||
| 			return nil, utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) | 			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() | 	defer res.Body.Close() | ||||||
| 	if res.StatusCode == 401 { | 	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.
 | 	// TODO: Right now we're ignoring checksums in the response body.
 | ||||||
| 	// In the future, we need to use them to check image validity.
 | 	// 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) | 	out = utils.NewWriteFlusher(out) | ||||||
| 	err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel) | 	err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel) | ||||||
|  | 	if err == registry.ErrLoginRequired { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { | 		if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { | ||||||
| 			return err | 			return err | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue