mirror of https://github.com/docker/docs.git
Merge branch 'mhennings-1357-implement-login-with-private-registry' into api_1_5
This commit is contained in:
commit
98a1314251
34
api.go
34
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,19 +484,32 @@ 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 := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := parseForm(r); err != nil {
|
if err := parseForm(r); err != nil {
|
||||||
return err
|
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 vars == nil {
|
if vars == nil {
|
||||||
return fmt.Errorf("Missing parameter")
|
return fmt.Errorf("Missing parameter")
|
||||||
|
|
97
auth/auth.go
97
auth/auth.go
|
@ -26,10 +26,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthConfig struct {
|
type AuthConfig struct {
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
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 {
|
||||||
status = "Account created. Please use the confirmation link we sent" +
|
if loginAgainstOfficialIndex {
|
||||||
" to your e-mail to activate it."
|
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 {
|
} else if reqStatusCode == 403 {
|
||||||
return "", fmt.Errorf("Login: Your account hasn't been activated. " +
|
if loginAgainstOfficialIndex {
|
||||||
"Please check your e-mail for a confirmation link.")
|
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 {
|
} 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)
|
||||||
|
}
|
||||||
|
|
133
commands.go
133
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"
|
||||||
|
@ -92,6 +94,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"},
|
||||||
|
@ -103,7 +106,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"},
|
||||||
} {
|
} {
|
||||||
|
@ -127,7 +129,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
|
||||||
|
@ -188,10 +190,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)
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,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
|
||||||
|
|
||||||
|
@ -263,10 +263,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 == "" {
|
||||||
|
@ -299,19 +306,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")
|
||||||
}
|
}
|
||||||
|
@ -328,15 +332,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
|
||||||
}
|
}
|
||||||
|
@ -792,7 +796,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
|
||||||
}
|
}
|
||||||
|
@ -813,6 +817,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
|
||||||
|
@ -826,22 +837,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{
|
||||||
|
base64.URLEncoding.EncodeToString(buf),
|
||||||
|
}
|
||||||
|
|
||||||
return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out)
|
return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
|
||||||
|
"X-Registry-Auth": registryAuthHeader,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := push(); err != nil {
|
if err := push(authConfig); err != nil {
|
||||||
if err.Error() == "Authentication is required." {
|
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
|
||||||
}
|
}
|
||||||
|
@ -865,11 +882,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1116,7 +1165,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
|
||||||
|
@ -1133,7 +1182,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
|
||||||
|
@ -1405,7 +1454,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
|
||||||
}
|
}
|
||||||
|
@ -1582,7 +1654,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{})
|
||||||
}
|
}
|
||||||
|
@ -1595,6 +1667,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