diff --git a/api.go b/api.go index 59d88a7a2..f4a96a030 100644 --- a/api.go +++ b/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" @@ -20,7 +21,7 @@ import ( "strings" ) -const APIVERSION = 1.4 +const APIVERSION = 1.5 const DEFAULTHTTPHOST = "127.0.0.1" const DEFAULTHTTPPORT = 4243 const DEFAULTUNIXSOCKET = "/var/run/docker.sock" @@ -326,8 +327,18 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h n = -1 } + var b []byte outs := srv.Containers(all, size, n, since, before) - b, err := json.Marshal(outs) + if version < 1.5 { + outs2 := []APIContainersOld{} + for _, ctnr := range outs { + outs2 = append(outs2, ctnr.ToLegacy()) + } + b, err = json.Marshal(outs2) + } else { + b, err = json.Marshal(outs) + } + if err != nil { return err } @@ -394,6 +405,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 +426,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,19 +494,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 { - authConfig := &auth.AuthConfig{} metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } - if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return err - } 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 vars == nil { return fmt.Errorf("Missing parameter") diff --git a/api_params.go b/api_params.go index df879f63e..6403bc6a2 100644 --- a/api_params.go +++ b/api_params.go @@ -1,5 +1,7 @@ package docker +import "encoding/json" + type APIHistory struct { ID string `json:"Id"` Tags []string `json:",omitempty"` @@ -42,6 +44,30 @@ type APIRmi struct { } type APIContainers struct { + ID string `json:"Id"` + Image string + Command string + Created int64 + Status string + Ports []APIPort + SizeRw int64 + SizeRootFs int64 +} + +func (self *APIContainers) ToLegacy() APIContainersOld { + return APIContainersOld{ + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, + SizeRootFs: self.SizeRootFs, + } +} + +type APIContainersOld struct { ID string `json:"Id"` Image string Command string @@ -67,7 +93,17 @@ type APIRun struct { } type APIPort struct { - Port string + PrivatePort int64 + PublicPort int64 + Type string +} + +func (port *APIPort) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "PrivatePort": port.PrivatePort, + "PublicPort": port.PublicPort, + "Type": port.Type, + }) } type APIVersion struct { diff --git a/auth/auth.go b/auth/auth.go index 91314877c..aff6de6dc 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -26,10 +26,11 @@ var ( ) type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth"` - Email string `json:"email"` + Username string `json:"username,omitempty"` + 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 { - status = "Account created. Please use the confirmation link we sent" + - " to your e-mail to activate it." + 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 { - return "", fmt.Errorf("Login: Your account hasn't been activated. " + - "Please check your e-mail for a confirmation link.") + 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) +} diff --git a/commands.go b/commands.go index df6feac93..00eeaf388 100644 --- a/commands.go +++ b/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" @@ -21,6 +23,7 @@ import ( "path/filepath" "reflect" "runtime" + "sort" "strconv" "strings" "syscall" @@ -91,6 +94,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 +106,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 +129,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 +190,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 +255,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 +263,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 +306,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 +332,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 } @@ -786,7 +791,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 } @@ -807,6 +812,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 @@ -820,22 +832,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 } + 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.Error() == "Authentication is required." { + 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 } @@ -859,11 +877,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 } @@ -953,6 +1003,19 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } +func displayablePorts(ports []APIPort) string { + result := []string{} + for _, port := range ports { + if port.Type == "tcp" { + result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort)) + } else { + result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type)) + } + } + sort.Strings(result) + return strings.Join(result, ", ") +} + func (cli *DockerCli) CmdPs(args ...string) error { cmd := Subcmd("ps", "[OPTIONS]", "List containers") quiet := cmd.Bool("q", false, "Only display numeric IDs") @@ -1010,9 +1073,9 @@ func (cli *DockerCli) CmdPs(args ...string) error { for _, out := range outs { if !*quiet { if *noTrunc { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) } if *size { if out.SizeRootFs > 0 { @@ -1097,7 +1160,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 @@ -1114,7 +1177,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 @@ -1386,7 +1449,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 } @@ -1573,7 +1659,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{}) } @@ -1586,6 +1672,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") { diff --git a/container.go b/container.go index 39c19c53d..5122fd7fc 100644 --- a/container.go +++ b/container.go @@ -15,7 +15,6 @@ import ( "os/exec" "path" "path/filepath" - "sort" "strconv" "strings" "syscall" @@ -253,17 +252,28 @@ type NetworkSettings struct { PortMapping map[string]PortMapping } -// String returns a human-readable description of the port mapping defined in the settings -func (settings *NetworkSettings) PortMappingHuman() string { - var mapping []string +// returns a more easy to process description of the port mapping defined in the settings +func (settings *NetworkSettings) PortMappingAPI() []APIPort { + var mapping []APIPort for private, public := range settings.PortMapping["Tcp"] { - mapping = append(mapping, fmt.Sprintf("%s->%s", public, private)) + pubint, _ := strconv.ParseInt(public, 0, 0) + privint, _ := strconv.ParseInt(private, 0, 0) + mapping = append(mapping, APIPort{ + PrivatePort: privint, + PublicPort: pubint, + Type: "tcp", + }) } for private, public := range settings.PortMapping["Udp"] { - mapping = append(mapping, fmt.Sprintf("%s->%s/udp", public, private)) + pubint, _ := strconv.ParseInt(public, 0, 0) + privint, _ := strconv.ParseInt(private, 0, 0) + mapping = append(mapping, APIPort{ + PrivatePort: privint, + PublicPort: pubint, + Type: "udp", + }) } - sort.Strings(mapping) - return strings.Join(mapping, ", ") + return mapping } // Inject the io.Reader at the given path. Note: do not close the reader diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 0563247dc..6712a4a57 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -27,14 +27,36 @@ Docker Remote API 2. Versions =========== -The current version of the API is 1.4 +The current version of the API is 1.5 Calling /images//insert is the same as calling -/v1.4/images//insert +/v1.5/images//insert You can still call an old version of the api using /v1.0/images//insert +:doc:`docker_remote_api_v1.5` +***************************** + +What's new +---------- + +.. http:post:: /images/create + + **New!** You can now pass registry credentials (via an AuthConfig object) + through the `X-Registry-Auth` header + +.. http:post:: /images/(name)/push + + **New!** The AuthConfig object now needs to be passed through + the `X-Registry-Auth` header + +.. http:get:: /containers/json + + **New!** The format of the `Ports` entry has been changed to a list of + dicts each containing `PublicPort`, `PrivatePort` and `Type` describing a + port mapping. + :doc:`docker_remote_api_v1.4` ***************************** diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 4f946e1e2..227c18f3d 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -993,7 +993,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**: diff --git a/docs/sources/api/docker_remote_api_v1.5.rst b/docs/sources/api/docker_remote_api_v1.5.rst new file mode 100644 index 000000000..4f994f5bf --- /dev/null +++ b/docs/sources/api/docker_remote_api_v1.5.rst @@ -0,0 +1,1175 @@ +:title: Remote API v1.5 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +:orphan: + +====================== +Docker Remote API v1.5 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker daemon is 4243 +- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Privileged": false, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"", + "WorkingDir":"" + + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"} + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a container +**************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/(format) + + List images ``format`` could be json or viz (json default) + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Repository":"base", + "Tag":"ubuntu-12.10", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + }, + { + "Repository":"base", + "Tag":"ubuntu-quantal", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + } + ] + + + **Example request**: + + .. sourcecode:: http + + GET /images/viz HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: text/plain + + digraph docker { + "d82cbacda43a" -> "074be284591f" + "1496068ca813" -> "08306dc45919" + "08306dc45919" -> "0e7893146ac2" + "b750fe79269d" -> "1496068ca813" + base -> "27cf78414709" [style=invis] + "f71189fff3de" -> "9a33b36209ed" + "27cf78414709" -> "b750fe79269d" + "0e7893146ac2" -> "d6434d954665" + "d6434d954665" -> "d82cbacda43a" + base -> "e9aa60c60128" [style=invis] + "074be284591f" -> "f71189fff3de" + "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + base [style=invisible] + } + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :statuscode 200: no error + :statuscode 500: server error + + +Insert a file in an image +************************* + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + The ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query registry: the registry you wan to push, optional + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + + The stream must be a tar archive compressed with one of the following algorithms: + identity (no compression), gzip, bzip2, xz. The archive must include a file called + `Dockerfile` at its root. It may include any number of other files, which will be + accessible in the build context (See the ADD build command). + + The Content-type header should be set to "application/tar". + + :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success + :query q: suppress verbose build output + :query nocache: do not use the cache when building the image + :statuscode 200: no error + :statuscode 500: server error + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + POST /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors + diff --git a/docs/sources/commandline/command/login.rst b/docs/sources/commandline/command/login.rst index 57ecaeb00..46f354d6b 100644 --- a/docs/sources/commandline/command/login.rst +++ b/docs/sources/commandline/command/login.rst @@ -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 + diff --git a/registry/registry.go b/registry/registry.go index 759652f07..f24e0b350 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -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. diff --git a/server.go b/server.go index f108fbaec..322d8fe93 100644 --- a/server.go +++ b/server.go @@ -387,7 +387,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) c.Created = container.Created.Unix() c.Status = container.State.String() - c.Ports = container.NetworkSettings.PortMappingHuman() + c.Ports = container.NetworkSettings.PortMappingAPI() if size { c.SizeRw, c.SizeRootFs = container.GetSize() } @@ -656,6 +656,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