summaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/api_unit_test.go46
-rw-r--r--api/client/cli.go102
-rw-r--r--api/client/commands.go (renamed from api/client.go)693
-rw-r--r--api/client/utils.go390
-rw-r--r--api/common.go11
-rw-r--r--api/server/server.go (renamed from api/server.go)104
-rw-r--r--api/server/server_unit_test.go180
7 files changed, 938 insertions, 588 deletions
diff --git a/api/api_unit_test.go b/api/api_unit_test.go
index 2b3e76e75c..678331d369 100644
--- a/api/api_unit_test.go
+++ b/api/api_unit_test.go
@@ -1,9 +1,6 @@
package api
import (
- "fmt"
- "net/http"
- "net/http/httptest"
"testing"
)
@@ -20,46 +17,3 @@ func TestJsonContentType(t *testing.T) {
t.Fail()
}
}
-
-func TestGetBoolParam(t *testing.T) {
- if ret, err := getBoolParam("true"); err != nil || !ret {
- t.Fatalf("true -> true, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam("True"); err != nil || !ret {
- t.Fatalf("True -> true, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam("1"); err != nil || !ret {
- t.Fatalf("1 -> true, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam(""); err != nil || ret {
- t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam("false"); err != nil || ret {
- t.Fatalf("false -> false, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam("0"); err != nil || ret {
- t.Fatalf("0 -> false, nil | got %t %s", ret, err)
- }
- if ret, err := getBoolParam("faux"); err == nil || ret {
- t.Fatalf("faux -> false, err | got %t %s", ret, err)
- }
-}
-
-func TesthttpError(t *testing.T) {
- r := httptest.NewRecorder()
-
- httpError(r, fmt.Errorf("No such method"))
- if r.Code != http.StatusNotFound {
- t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
- }
-
- httpError(r, fmt.Errorf("This accound hasn't been activated"))
- if r.Code != http.StatusForbidden {
- t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
- }
-
- httpError(r, fmt.Errorf("Some error"))
- if r.Code != http.StatusInternalServerError {
- t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
- }
-}
diff --git a/api/client/cli.go b/api/client/cli.go
new file mode 100644
index 0000000000..b58d3c3c75
--- /dev/null
+++ b/api/client/cli.go
@@ -0,0 +1,102 @@
+package client
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "strings"
+ "text/template"
+
+ flag "github.com/dotcloud/docker/pkg/mflag"
+ "github.com/dotcloud/docker/pkg/term"
+ "github.com/dotcloud/docker/registry"
+)
+
+var funcMap = template.FuncMap{
+ "json": func(v interface{}) string {
+ a, _ := json.Marshal(v)
+ return string(a)
+ },
+}
+
+func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
+ methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
+ method := reflect.ValueOf(cli).MethodByName(methodName)
+ if !method.IsValid() {
+ return nil, false
+ }
+ return method.Interface().(func(...string) error), true
+}
+
+func (cli *DockerCli) ParseCommands(args ...string) error {
+ if len(args) > 0 {
+ method, exists := cli.getMethod(args[0])
+ if !exists {
+ fmt.Println("Error: Command not found:", args[0])
+ return cli.CmdHelp(args[1:]...)
+ }
+ return method(args[1:]...)
+ }
+ return cli.CmdHelp(args...)
+}
+
+func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
+ flags := flag.NewFlagSet(name, flag.ContinueOnError)
+ flags.Usage = func() {
+ fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
+ flags.PrintDefaults()
+ os.Exit(2)
+ }
+ return flags
+}
+
+func (cli *DockerCli) LoadConfigFile() (err error) {
+ cli.configFile, err = registry.LoadConfig(os.Getenv("HOME"))
+ if err != nil {
+ fmt.Fprintf(cli.err, "WARNING: %s\n", err)
+ }
+ return err
+}
+
+func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string, tlsConfig *tls.Config) *DockerCli {
+ var (
+ isTerminal = false
+ terminalFd uintptr
+ )
+
+ if in != nil {
+ if file, ok := in.(*os.File); ok {
+ terminalFd = file.Fd()
+ isTerminal = term.IsTerminal(terminalFd)
+ }
+ }
+
+ if err == nil {
+ err = out
+ }
+ return &DockerCli{
+ proto: proto,
+ addr: addr,
+ in: in,
+ out: out,
+ err: err,
+ isTerminal: isTerminal,
+ terminalFd: terminalFd,
+ tlsConfig: tlsConfig,
+ }
+}
+
+type DockerCli struct {
+ proto string
+ addr string
+ configFile *registry.ConfigFile
+ in io.ReadCloser
+ out io.Writer
+ err io.Writer
+ isTerminal bool
+ terminalFd uintptr
+ tlsConfig *tls.Config
+}
diff --git a/api/client.go b/api/client/commands.go
index 10075ae613..443917d3fb 100644
--- a/api/client.go
+++ b/api/client/commands.go
@@ -1,76 +1,38 @@
-package api
+package client
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
- "errors"
"fmt"
- "github.com/dotcloud/docker/archive"
- "github.com/dotcloud/docker/auth"
- "github.com/dotcloud/docker/dockerversion"
- "github.com/dotcloud/docker/engine"
- "github.com/dotcloud/docker/nat"
- flag "github.com/dotcloud/docker/pkg/mflag"
- "github.com/dotcloud/docker/pkg/term"
- "github.com/dotcloud/docker/registry"
- "github.com/dotcloud/docker/runconfig"
- "github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
- "net"
"net/http"
- "net/http/httputil"
"net/url"
"os"
- "os/signal"
+ "os/exec"
"path"
- "reflect"
- "regexp"
- "runtime"
+ goruntime "runtime"
"strconv"
"strings"
"syscall"
"text/tabwriter"
"text/template"
"time"
-)
-var funcMap = template.FuncMap{
- "json": func(v interface{}) string {
- a, _ := json.Marshal(v)
- return string(a)
- },
-}
-
-var (
- ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
+ "github.com/dotcloud/docker/api"
+ "github.com/dotcloud/docker/archive"
+ "github.com/dotcloud/docker/dockerversion"
+ "github.com/dotcloud/docker/engine"
+ "github.com/dotcloud/docker/nat"
+ "github.com/dotcloud/docker/pkg/signal"
+ "github.com/dotcloud/docker/pkg/term"
+ "github.com/dotcloud/docker/registry"
+ "github.com/dotcloud/docker/runconfig"
+ "github.com/dotcloud/docker/utils"
)
-func (cli *DockerCli) getMethod(name string) (func(...string) error, bool) {
- methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
- method := reflect.ValueOf(cli).MethodByName(methodName)
- if !method.IsValid() {
- return nil, false
- }
- return method.Interface().(func(...string) error), true
-}
-
-func ParseCommands(proto, addr string, args ...string) error {
- cli := NewDockerCli(os.Stdin, os.Stdout, os.Stderr, proto, addr)
-
- if len(args) > 0 {
- method, exists := cli.getMethod(args[0])
- if !exists {
- fmt.Println("Error: Command not found:", args[0])
- return cli.CmdHelp(args[1:]...)
- }
- return method(args[1:]...)
- }
- return cli.CmdHelp(args...)
-}
-
func (cli *DockerCli) CmdHelp(args ...string) error {
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
@@ -81,7 +43,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil
}
}
- help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTUNIXSOCKET)
+ help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET)
for _, command := range [][]string{
{"attach", "Attach to a running container"},
{"build", "Build a container from a Dockerfile"},
@@ -94,7 +56,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"images", "List images"},
{"import", "Create a new filesystem image from the contents of a tarball"},
{"info", "Display system-wide information"},
- {"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"load", "Load an image from a tar archive"},
@@ -123,7 +84,9 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
return nil
}
+// FIXME: 'insert' is deprecated.
func (cli *DockerCli) CmdInsert(args ...string) error {
+ fmt.Fprintf(os.Stderr, "Warning: '%s' is deprecated and will be removed in a future version. Please use 'docker build' and 'ADD' instead.\n")
cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
if err := cmd.Parse(args); err != nil {
return nil
@@ -160,6 +123,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
err error
)
+ _, err = exec.LookPath("git")
+ hasGit := err == nil
if cmd.Arg(0) == "-" {
// As a special case, 'docker build -' will build from an empty context with the
// contents of stdin as a Dockerfile
@@ -168,17 +133,34 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
return err
}
context, err = archive.Generate("Dockerfile", string(dockerfile))
- } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
+ } else if utils.IsURL(cmd.Arg(0)) && (!utils.IsGIT(cmd.Arg(0)) || !hasGit) {
isRemote = true
} else {
- if _, err := os.Stat(cmd.Arg(0)); err != nil {
+ root := cmd.Arg(0)
+ if utils.IsGIT(root) {
+ remoteURL := cmd.Arg(0)
+ if !strings.HasPrefix(remoteURL, "git://") && !strings.HasPrefix(remoteURL, "git@") && !utils.IsURL(remoteURL) {
+ remoteURL = "https://" + remoteURL
+ }
+
+ root, err = ioutil.TempDir("", "docker-build-git")
+ if err != nil {
+ return err
+ }
+ defer os.RemoveAll(root)
+
+ if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil {
+ return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
+ }
+ }
+ if _, err := os.Stat(root); err != nil {
return err
}
- filename := path.Join(cmd.Arg(0), "Dockerfile")
+ filename := path.Join(root, "Dockerfile")
if _, err = os.Stat(filename); os.IsNotExist(err) {
return fmt.Errorf("no Dockerfile found in %s", cmd.Arg(0))
}
- context, err = archive.Tar(cmd.Arg(0), archive.Uncompressed)
+ context, err = archive.Tar(root, archive.Uncompressed)
}
var body io.Reader
// Setup an upload progress bar
@@ -189,6 +171,15 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
}
// Upload the build context
v := &url.Values{}
+
+ //Check if the given image name can be resolved
+ if *tag != "" {
+ repository, _ := utils.ParseRepositoryTag(*tag)
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+ return err
+ }
+ }
+
v.Set("t", *tag)
if *suppressOutput {
@@ -229,7 +220,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 := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
+ cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+registry.IndexServerAddress()+"\" is the default.")
var username, password, email string
@@ -240,7 +231,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
if err != nil {
return nil
}
- serverAddress := auth.IndexServerAddress()
+ serverAddress := registry.IndexServerAddress()
if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0)
}
@@ -266,7 +257,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
cli.LoadConfigFile()
authconfig, ok := cli.configFile.Configs[serverAddress]
if !ok {
- authconfig = auth.AuthConfig{}
+ authconfig = registry.AuthConfig{}
}
if username == "" {
@@ -311,7 +302,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)
if statusCode == 401 {
delete(cli.configFile.Configs, serverAddress)
- auth.SaveConfig(cli.configFile)
+ registry.SaveConfig(cli.configFile)
return err
}
if err != nil {
@@ -320,10 +311,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
var out2 engine.Env
err = out2.Decode(stream)
if err != nil {
- cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
+ cli.configFile, _ = registry.LoadConfig(os.Getenv("HOME"))
return err
}
- auth.SaveConfig(cli.configFile)
+ registry.SaveConfig(cli.configFile)
if out2.Get("Status") != "" {
fmt.Fprintf(cli.out, "%s\n", out2.Get("Status"))
}
@@ -367,7 +358,8 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
if dockerversion.VERSION != "" {
fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION)
}
- fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version())
+ fmt.Fprintf(cli.out, "Client API version: %s\n", api.APIVERSION)
+ fmt.Fprintf(cli.out, "Go version (client): %s\n", goruntime.Version())
if dockerversion.GITCOMMIT != "" {
fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT)
}
@@ -389,6 +381,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
}
out.Close()
fmt.Fprintf(cli.out, "Server version: %s\n", remoteVersion.Get("Version"))
+ if apiVersion := remoteVersion.Get("ApiVersion"); apiVersion != "" {
+ fmt.Fprintf(cli.out, "Server API version: %s\n", apiVersion)
+ }
fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit"))
fmt.Fprintf(cli.out, "Go version (server): %s\n", remoteVersion.Get("GoVersion"))
release := utils.GetReleaseVersion()
@@ -432,7 +427,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers"))
fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images"))
- fmt.Fprintf(cli.out, "Driver: %s\n", remoteInfo.Get("Driver"))
+ fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver"))
var driverStatus [][2]string
if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil {
return err
@@ -440,14 +435,15 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
for _, pair := range driverStatus {
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
}
+ fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
+ fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
+
if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", remoteInfo.GetBool("Debug"))
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd"))
fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines"))
- fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver"))
fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener"))
- fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion"))
if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" {
fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1)
@@ -533,13 +529,23 @@ func (cli *DockerCli) CmdRestart(args ...string) error {
func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
sigc := make(chan os.Signal, 1)
- utils.CatchAll(sigc)
+ signal.CatchAll(sigc)
go func() {
for s := range sigc {
if s == syscall.SIGCHLD {
continue
}
- if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil, false)); err != nil {
+ var sig string
+ for sigStr, sigN := range signal.SignalMap {
+ if sigN == s {
+ sig = sigStr
+ break
+ }
+ }
+ if sig == "" {
+ utils.Errorf("Unsupported signal: %d. Discarding.", s)
+ }
+ if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil {
utils.Debugf("Error sending signal: %s", err)
}
}
@@ -548,9 +554,11 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
}
func (cli *DockerCli) CmdStart(args ...string) error {
- cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
- attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process")
- openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin")
+ var (
+ cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
+ attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process")
+ openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin")
+ )
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -559,8 +567,10 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return nil
}
- var cErr chan error
- var tty bool
+ var (
+ cErr chan error
+ tty bool
+ )
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("You cannot start and attach multiple containers at once.")
@@ -571,7 +581,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return err
}
- container := &Container{}
+ container := &api.Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
@@ -581,7 +591,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
if !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
- defer utils.StopCatch(sigc)
+ defer signal.StopCatch(sigc)
}
var in io.ReadCloser
@@ -606,8 +616,8 @@ func (cli *DockerCli) CmdStart(args ...string) error {
if err != nil {
if !*attach || !*openStdin {
fmt.Fprintf(cli.err, "%s\n", err)
- encounteredError = fmt.Errorf("Error: failed to start one or more containers")
}
+ encounteredError = fmt.Errorf("Error: failed to start one or more containers")
} else {
if !*attach || !*openStdin {
fmt.Fprintf(cli.out, "%s\n", name)
@@ -758,9 +768,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
return nil
}
- port := cmd.Arg(1)
- proto := "tcp"
- parts := strings.SplitN(port, "/", 2)
+ var (
+ port = cmd.Arg(1)
+ proto = "tcp"
+ parts = strings.SplitN(port, "/", 2)
+ container api.Container
+ )
+
if len(parts) == 2 && len(parts[1]) != 0 {
port = parts[0]
proto = parts[1]
@@ -769,13 +783,13 @@ func (cli *DockerCli) CmdPort(args ...string) error {
if err != nil {
return err
}
- var out Container
- err = json.Unmarshal(body, &out)
+
+ err = json.Unmarshal(body, &container)
if err != nil {
return err
}
- if frontends, exists := out.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
+ if frontends, exists := container.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil {
for _, frontend := range frontends {
fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort)
}
@@ -788,8 +802,9 @@ func (cli *DockerCli) CmdPort(args ...string) error {
// 'docker rmi IMAGE' removes all images with the name IMAGE
func (cli *DockerCli) CmdRmi(args ...string) error {
var (
- cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
- force = cmd.Bool([]string{"f", "-force"}, false, "Force")
+ cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
+ force = cmd.Bool([]string{"f", "-force"}, false, "Force")
+ noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
)
if err := cmd.Parse(args); err != nil {
return nil
@@ -803,6 +818,9 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
if *force {
v.Set("force", "1")
}
+ if *noprune {
+ v.Set("noprune", "1")
+ }
var encounteredError error
for _, name := range cmd.Args() {
@@ -969,6 +987,14 @@ func (cli *DockerCli) CmdImport(args ...string) error {
repository, tag = utils.ParseRepositoryTag(cmd.Arg(1))
}
v := url.Values{}
+
+ if repository != "" {
+ //Check if the given image name can be resolved
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+ return err
+ }
+ }
+
v.Set("repo", repository)
v.Set("tag", tag)
v.Set("fromSrc", src)
@@ -983,7 +1009,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
}
func (cli *DockerCli) CmdPush(args ...string) error {
- cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
+ cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -996,8 +1022,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
cli.LoadConfigFile()
+ remote, tag := utils.ParseRepositoryTag(name)
+
// Resolve the Repository name from fqn to hostname + name
- hostname, _, err := registry.ResolveRepositoryName(name)
+ hostname, _, err := registry.ResolveRepositoryName(remote)
if err != nil {
return err
}
@@ -1008,7 +1036,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
// Custom repositories can have different rules, and we must also
// allow pushing by image ID.
if len(strings.SplitN(name, "/", 2)) == 1 {
- username := cli.configFile.Configs[auth.IndexServerAddress()].Username
+ username := cli.configFile.Configs[registry.IndexServerAddress()].Username
if username == "" {
username = "<user>"
}
@@ -1016,7 +1044,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
}
v := url.Values{}
- push := func(authConfig auth.AuthConfig) error {
+ v.Set("tag", tag)
+ push := func(authConfig registry.AuthConfig) error {
buf, err := json.Marshal(authConfig)
if err != nil {
return err
@@ -1025,7 +1054,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
base64.URLEncoding.EncodeToString(buf),
}
- return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
+ return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{
"X-Registry-Auth": registryAuthHeader,
})
}
@@ -1045,8 +1074,8 @@ func (cli *DockerCli) CmdPush(args ...string) error {
}
func (cli *DockerCli) CmdPull(args ...string) error {
- cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
- tag := cmd.String([]string{"t", "-tag"}, "", "Download tagged image in repository")
+ cmd := cli.Subcmd("pull", "NAME[:TAG]", "Pull an image or a repository from the registry")
+ tag := cmd.String([]string{"#t", "#-tag"}, "", "Download tagged image in repository")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1075,7 +1104,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
v.Set("fromImage", remote)
v.Set("tag", *tag)
- pull := func(authConfig auth.AuthConfig) error {
+ pull := func(authConfig registry.AuthConfig) error {
buf, err := json.Marshal(authConfig)
if err != nil {
return err
@@ -1107,10 +1136,11 @@ func (cli *DockerCli) CmdPull(args ...string) error {
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
- all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate images used to build)")
+ all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (by default filter out the intermediate image layers)")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
- flViz := cmd.Bool([]string{"v", "#viz", "-viz"}, false, "Output graph in graphviz format")
- flTree := cmd.Bool([]string{"t", "#tree", "-tree"}, false, "Output graph in tree format")
+ // FIXME: --viz and --tree are deprecated. Remove them in a future version.
+ flViz := cmd.Bool([]string{"#v", "#viz", "#-viz"}, false, "Output graph in graphviz format")
+ flTree := cmd.Bool([]string{"#t", "#tree", "#-tree"}, false, "Output graph in tree format")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1122,6 +1152,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
filter := cmd.Arg(0)
+ // FIXME: --viz and --tree are deprecated. Remove them in a future version.
if *flViz || *flTree {
body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false))
if err != nil {
@@ -1232,6 +1263,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
return nil
}
+// FIXME: --viz and --tree are deprecated. Remove them in a future version.
func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) {
length := images.Len()
if length > 1 {
@@ -1258,6 +1290,7 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[
}
}
+// FIXME: --viz and --tree are deprecated. Remove them in a future version.
func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) {
var (
imageID string
@@ -1281,6 +1314,7 @@ func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix strin
}
}
+// FIXME: --viz and --tree are deprecated. Remove them in a future version.
func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) {
var imageID string
if noTrunc {
@@ -1304,8 +1338,8 @@ func (cli *DockerCli) CmdPs(args ...string) error {
all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.")
- since := cmd.String([]string{"#sinceId", "-since-id"}, "", "Show only containers created since Id, include non-running ones.")
- before := cmd.String([]string{"#beforeId", "-before-id"}, "", "Show only container created before Id, include non-running ones.")
+ since := cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show only containers created since Id or Name, include non-running ones.")
+ before := cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name, include non-running ones.")
last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.")
if err := cmd.Parse(args); err != nil {
@@ -1374,7 +1408,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
outCommand = utils.Trunc(outCommand, 20)
}
ports.ReadListFrom([]byte(out.Get("Ports")))
- fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), displayablePorts(ports), strings.Join(outNames, ","))
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ","))
if *size {
if out.GetInt("SizeRootFs") > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs")))
@@ -1399,7 +1433,8 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
- flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
+ // FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
+ flConfig := cmd.String([]string{"#run", "#-run"}, "", "this option is deprecated and will be removed in a future version in favor of inline Dockerfile-compatible commands")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1419,6 +1454,13 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
return nil
}
+ //Check if the given image name can be resolved
+ if repository != "" {
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+ return err
+ }
+ }
+
v := url.Values{}
v.Set("container", name)
v.Set("repo", repository)
@@ -1548,7 +1590,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
return err
}
- container := &Container{}
+ container := &api.Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
@@ -1585,7 +1627,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err
}
- container := &Container{}
+ container := &api.Container{}
err = json.Unmarshal(body, container)
if err != nil {
return err
@@ -1614,7 +1656,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
if *proxy && !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
- defer utils.StopCatch(sigc)
+ defer signal.StopCatch(sigc)
}
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, in, cli.out, cli.err, nil); err != nil {
@@ -1707,6 +1749,11 @@ func (cli *DockerCli) CmdTag(args ...string) error {
}
v := url.Values{}
+
+ //Check if the given image name can be resolved
+ if _, _, err := registry.ResolveRepositoryName(repository); err != nil {
+ return err
+ }
v.Set("repo", repository)
v.Set("tag", tag)
@@ -1753,7 +1800,21 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
return fmt.Errorf("Failed to create the container ID file: %s", err)
}
- defer containerIDFile.Close()
+ defer func() {
+ containerIDFile.Close()
+ var (
+ cidFileInfo os.FileInfo
+ err error
+ )
+ if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
+ return
+ }
+ if cidFileInfo.Size() == 0 {
+ if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
+ fmt.Printf("failed to remove CID file '%s': %s \n", hostConfig.ContainerIDFile, err)
+ }
+ }
+ }()
}
containerValues := url.Values{}
@@ -1818,7 +1879,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
if sigProxy {
sigc := cli.forwardAllSignals(runResult.Get("Id"))
- defer utils.StopCatch(sigc)
+ defer signal.StopCatch(sigc)
}
var (
@@ -1996,7 +2057,9 @@ func (cli *DockerCli) CmdCp(args ...string) error {
}
func (cli *DockerCli) CmdSave(args ...string) error {
- cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout)")
+ cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout by default)")
+ outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
+
if err := cmd.Parse(args); err != nil {
return err
}
@@ -2006,8 +2069,18 @@ func (cli *DockerCli) CmdSave(args ...string) error {
return nil
}
+ var (
+ output io.Writer = cli.out
+ err error
+ )
+ if *outfile != "" {
+ output, err = os.Create(*outfile)
+ if err != nil {
+ return err
+ }
+ }
image := cmd.Arg(0)
- if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
+ if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil {
return err
}
return nil
@@ -2015,6 +2088,8 @@ func (cli *DockerCli) CmdSave(args ...string) error {
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN")
+ infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
+
if err := cmd.Parse(args); err != nil {
return err
}
@@ -2024,408 +2099,18 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
return nil
}
- if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
- return err
- }
- return nil
-}
-
-func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
- params := bytes.NewBuffer(nil)
- if data != nil {
- if env, ok := data.(engine.Env); ok {
- if err := env.Encode(params); err != nil {
- return nil, -1, err
- }
- } else {
- buf, err := json.Marshal(data)
- if err != nil {
- return nil, -1, err
- }
- if _, err := params.Write(buf); err != nil {
- return nil, -1, err
- }
- }
- }
- // fixme: refactor client to support redirect
- re := regexp.MustCompile("/+")
- path = re.ReplaceAllString(path, "/")
-
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), params)
- if err != nil {
- return nil, -1, err
- }
- if passAuthInfo {
- cli.LoadConfigFile()
- // Resolve the Auth config relevant for this server
- authConfig := cli.configFile.ResolveAuthConfig(auth.IndexServerAddress())
- getHeaders := func(authConfig auth.AuthConfig) (map[string][]string, error) {
- buf, err := json.Marshal(authConfig)
- if err != nil {
- return nil, err
- }
- registryAuthHeader := []string{
- base64.URLEncoding.EncodeToString(buf),
- }
- return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
- }
- if headers, err := getHeaders(authConfig); err == nil && headers != nil {
- for k, v := range headers {
- req.Header[k] = v
- }
- }
- }
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
- req.Host = cli.addr
- if data != nil {
- req.Header.Set("Content-Type", "application/json")
- } else if method == "POST" {
- req.Header.Set("Content-Type", "plain/text")
- }
- dial, err := net.Dial(cli.proto, cli.addr)
- if err != nil {
- if strings.Contains(err.Error(), "connection refused") {
- return nil, -1, ErrConnectionRefused
- }
- return nil, -1, err
- }
- clientconn := httputil.NewClientConn(dial, nil)
- resp, err := clientconn.Do(req)
- if err != nil {
- clientconn.Close()
- if strings.Contains(err.Error(), "connection refused") {
- return nil, -1, ErrConnectionRefused
- }
- return nil, -1, err
- }
-
- if resp.StatusCode < 200 || resp.StatusCode >= 400 {
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, -1, err
- }
- if len(body) == 0 {
- return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
- }
- return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
- }
-
- wrapper := utils.NewReadCloserWrapper(resp.Body, func() error {
- if resp != nil && resp.Body != nil {
- resp.Body.Close()
- }
- return clientconn.Close()
- })
- return wrapper, resp.StatusCode, nil
-}
-
-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{})
- }
-
- // fixme: refactor client to support redirect
- re := regexp.MustCompile("/+")
- path = re.ReplaceAllString(path, "/")
-
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), in)
- if err != nil {
- return err
- }
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
- req.Host = cli.addr
- 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") {
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
- }
- return err
- }
- clientconn := httputil.NewClientConn(dial, nil)
- resp, err := clientconn.Do(req)
- defer clientconn.Close()
- if err != nil {
- if strings.Contains(err.Error(), "connection refused") {
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
- }
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode < 200 || resp.StatusCode >= 400 {
- body, err := ioutil.ReadAll(resp.Body)
+ var (
+ input io.Reader = cli.in
+ err error
+ )
+ if *infile != "" {
+ input, err = os.Open(*infile)
if err != nil {
return err
}
- if len(body) == 0 {
- return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
- }
- return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
- }
-
- if MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
- return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal)
}
- if _, err := io.Copy(out, resp.Body); err != nil {
- return err
- }
- return nil
-}
-
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
- defer func() {
- if started != nil {
- close(started)
- }
- }()
- // fixme: refactor client to support redirect
- re := regexp.MustCompile("/+")
- path = re.ReplaceAllString(path, "/")
-
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), nil)
- if err != nil {
+ if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil {
return err
}
- req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
- req.Header.Set("Content-Type", "plain/text")
- req.Host = cli.addr
-
- dial, err := net.Dial(cli.proto, cli.addr)
- if err != nil {
- if strings.Contains(err.Error(), "connection refused") {
- return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
- }
- return err
- }
- clientconn := httputil.NewClientConn(dial, nil)
- defer clientconn.Close()
-
- // Server hijacks the connection, error 'connection closed' expected
- clientconn.Do(req)
-
- rwc, br := clientconn.Hijack()
- defer rwc.Close()
-
- if started != nil {
- started <- rwc
- }
-
- var receiveStdout chan error
-
- var oldState *term.State
-
- if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
- oldState, err = term.SetRawTerminal(cli.terminalFd)
- if err != nil {
- return err
- }
- defer term.RestoreTerminal(cli.terminalFd, oldState)
- }
-
- if stdout != nil || stderr != nil {
- receiveStdout = utils.Go(func() (err error) {
- defer func() {
- if in != nil {
- if setRawTerminal && cli.isTerminal {
- term.RestoreTerminal(cli.terminalFd, oldState)
- }
- in.Close()
- }
- }()
-
- // When TTY is ON, use regular copy
- if setRawTerminal {
- _, err = io.Copy(stdout, br)
- } else {
- _, err = utils.StdCopy(stdout, stderr, br)
- }
- utils.Debugf("[hijack] End of stdout")
- return err
- })
- }
-
- sendStdin := utils.Go(func() error {
- if in != nil {
- io.Copy(rwc, in)
- utils.Debugf("[hijack] End of stdin")
- }
- if tcpc, ok := rwc.(*net.TCPConn); ok {
- if err := tcpc.CloseWrite(); err != nil {
- utils.Errorf("Couldn't send EOF: %s\n", err)
- }
- } else if unixc, ok := rwc.(*net.UnixConn); ok {
- if err := unixc.CloseWrite(); err != nil {
- utils.Errorf("Couldn't send EOF: %s\n", err)
- }
- }
- // Discard errors due to pipe interruption
- return nil
- })
-
- if stdout != nil || stderr != nil {
- if err := <-receiveStdout; err != nil {
- utils.Errorf("Error receiveStdout: %s", err)
- return err
- }
- }
-
- if !cli.isTerminal {
- if err := <-sendStdin; err != nil {
- utils.Errorf("Error sendStdin: %s", err)
- return err
- }
- }
return nil
-
-}
-
-func (cli *DockerCli) getTtySize() (int, int) {
- if !cli.isTerminal {
- return 0, 0
- }
- ws, err := term.GetWinsize(cli.terminalFd)
- if err != nil {
- utils.Errorf("Error getting size: %s", err)
- if ws == nil {
- return 0, 0
- }
- }
- return int(ws.Height), int(ws.Width)
-}
-
-func (cli *DockerCli) resizeTty(id string) {
- height, width := cli.getTtySize()
- if height == 0 && width == 0 {
- return
- }
- v := url.Values{}
- v.Set("h", strconv.Itoa(height))
- v.Set("w", strconv.Itoa(width))
- if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
- utils.Errorf("Error resize: %s", err)
- }
-}
-
-func (cli *DockerCli) monitorTtySize(id string) error {
- cli.resizeTty(id)
-
- sigchan := make(chan os.Signal, 1)
- signal.Notify(sigchan, syscall.SIGWINCH)
- go func() {
- for _ = range sigchan {
- cli.resizeTty(id)
- }
- }()
- return nil
-}
-
-func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
- flags := flag.NewFlagSet(name, flag.ContinueOnError)
- flags.Usage = func() {
- fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
- flags.PrintDefaults()
- os.Exit(2)
- }
- return flags
-}
-
-func (cli *DockerCli) LoadConfigFile() (err error) {
- cli.configFile, err = auth.LoadConfig(os.Getenv("HOME"))
- if err != nil {
- fmt.Fprintf(cli.err, "WARNING: %s\n", err)
- }
- return err
-}
-
-func waitForExit(cli *DockerCli, containerId string) (int, error) {
- stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
- if err != nil {
- return -1, err
- }
-
- var out engine.Env
- if err := out.Decode(stream); err != nil {
- return -1, err
- }
- return out.GetInt("StatusCode"), nil
-}
-
-// getExitCode perform an inspect on the container. It returns
-// the running state and the exit code.
-func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
- body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false))
- if err != nil {
- // If we can't connect, then the daemon probably died.
- if err != ErrConnectionRefused {
- return false, -1, err
- }
- return false, -1, nil
- }
- c := &Container{}
- if err := json.Unmarshal(body, c); err != nil {
- return false, -1, err
- }
- return c.State.Running, c.State.ExitCode, nil
-}
-
-func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
- if stream != nil {
- defer stream.Close()
- }
- if err != nil {
- return nil, statusCode, err
- }
- body, err := ioutil.ReadAll(stream)
- if err != nil {
- return nil, -1, err
- }
- return body, statusCode, nil
-}
-
-func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
- var (
- isTerminal = false
- terminalFd uintptr
- )
-
- if in != nil {
- if file, ok := in.(*os.File); ok {
- terminalFd = file.Fd()
- isTerminal = term.IsTerminal(terminalFd)
- }
- }
-
- if err == nil {
- err = out
- }
- return &DockerCli{
- proto: proto,
- addr: addr,
- in: in,
- out: out,
- err: err,
- isTerminal: isTerminal,
- terminalFd: terminalFd,
- }
-}
-
-type DockerCli struct {
- proto string
- addr string
- configFile *auth.ConfigFile
- in io.ReadCloser
- out io.Writer
- err io.Writer
- isTerminal bool
- terminalFd uintptr
}
diff --git a/api/client/utils.go b/api/client/utils.go
new file mode 100644
index 0000000000..4ef09ba783
--- /dev/null
+++ b/api/client/utils.go
@@ -0,0 +1,390 @@
+package client
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ gosignal "os/signal"
+ "regexp"
+ goruntime "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "github.com/dotcloud/docker/api"
+ "github.com/dotcloud/docker/dockerversion"
+ "github.com/dotcloud/docker/engine"
+ "github.com/dotcloud/docker/pkg/term"
+ "github.com/dotcloud/docker/registry"
+ "github.com/dotcloud/docker/utils"
+)
+
+var (
+ ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
+)
+
+func (cli *DockerCli) dial() (net.Conn, error) {
+ if cli.tlsConfig != nil && cli.proto != "unix" {
+ return tls.Dial(cli.proto, cli.addr, cli.tlsConfig)
+ }
+ return net.Dial(cli.proto, cli.addr)
+}
+
+func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
+ params := bytes.NewBuffer(nil)
+ if data != nil {
+ if env, ok := data.(engine.Env); ok {
+ if err := env.Encode(params); err != nil {
+ return nil, -1, err
+ }
+ } else {
+ buf, err := json.Marshal(data)
+ if err != nil {
+ return nil, -1, err
+ }
+ if _, err := params.Write(buf); err != nil {
+ return nil, -1, err
+ }
+ }
+ }
+ // fixme: refactor client to support redirect
+ re := regexp.MustCompile("/+")
+ path = re.ReplaceAllString(path, "/")
+
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
+ if err != nil {
+ return nil, -1, err
+ }
+ if passAuthInfo {
+ cli.LoadConfigFile()
+ // Resolve the Auth config relevant for this server
+ authConfig := cli.configFile.ResolveAuthConfig(registry.IndexServerAddress())
+ getHeaders := func(authConfig registry.AuthConfig) (map[string][]string, error) {
+ buf, err := json.Marshal(authConfig)
+ if err != nil {
+ return nil, err
+ }
+ registryAuthHeader := []string{
+ base64.URLEncoding.EncodeToString(buf),
+ }
+ return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil
+ }
+ if headers, err := getHeaders(authConfig); err == nil && headers != nil {
+ for k, v := range headers {
+ req.Header[k] = v
+ }
+ }
+ }
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
+ req.Host = cli.addr
+ if data != nil {
+ req.Header.Set("Content-Type", "application/json")
+ } else if method == "POST" {
+ req.Header.Set("Content-Type", "plain/text")
+ }
+ dial, err := cli.dial()
+ if err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return nil, -1, ErrConnectionRefused
+ }
+ return nil, -1, err
+ }
+ clientconn := httputil.NewClientConn(dial, nil)
+ resp, err := clientconn.Do(req)
+ if err != nil {
+ clientconn.Close()
+ if strings.Contains(err.Error(), "connection refused") {
+ return nil, -1, ErrConnectionRefused
+ }
+ return nil, -1, err
+ }
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, -1, err
+ }
+ if len(body) == 0 {
+ return nil, resp.StatusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(resp.StatusCode), req.URL)
+ }
+ return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body))
+ }
+
+ wrapper := utils.NewReadCloserWrapper(resp.Body, func() error {
+ if resp != nil && resp.Body != nil {
+ resp.Body.Close()
+ }
+ return clientconn.Close()
+ })
+ return wrapper, resp.StatusCode, nil
+}
+
+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{})
+ }
+
+ // fixme: refactor client to support redirect
+ re := regexp.MustCompile("/+")
+ path = re.ReplaceAllString(path, "/")
+
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
+ req.Host = cli.addr
+ if method == "POST" {
+ req.Header.Set("Content-Type", "plain/text")
+ }
+
+ if headers != nil {
+ for k, v := range headers {
+ req.Header[k] = v
+ }
+ }
+
+ dial, err := cli.dial()
+ if err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
+ }
+ return err
+ }
+ clientconn := httputil.NewClientConn(dial, nil)
+ resp, err := clientconn.Do(req)
+ defer clientconn.Close()
+ if err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
+ }
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode < 200 || resp.StatusCode >= 400 {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ if len(body) == 0 {
+ return fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode))
+ }
+ return fmt.Errorf("Error: %s", bytes.TrimSpace(body))
+ }
+
+ if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") {
+ return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal)
+ }
+ if _, err := io.Copy(out, resp.Body); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
+ defer func() {
+ if started != nil {
+ close(started)
+ }
+ }()
+ // fixme: refactor client to support redirect
+ re := regexp.MustCompile("/+")
+ path = re.ReplaceAllString(path, "/")
+
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION)
+ req.Header.Set("Content-Type", "plain/text")
+ req.Host = cli.addr
+
+ dial, err := cli.dial()
+ if err != nil {
+ if strings.Contains(err.Error(), "connection refused") {
+ return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
+ }
+ return err
+ }
+ clientconn := httputil.NewClientConn(dial, nil)
+ defer clientconn.Close()
+
+ // Server hijacks the connection, error 'connection closed' expected
+ clientconn.Do(req)
+
+ rwc, br := clientconn.Hijack()
+ defer rwc.Close()
+
+ if started != nil {
+ started <- rwc
+ }
+
+ var receiveStdout chan error
+
+ var oldState *term.State
+
+ if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" {
+ oldState, err = term.SetRawTerminal(cli.terminalFd)
+ if err != nil {
+ return err
+ }
+ defer term.RestoreTerminal(cli.terminalFd, oldState)
+ }
+
+ if stdout != nil || stderr != nil {
+ receiveStdout = utils.Go(func() (err error) {
+ defer func() {
+ if in != nil {
+ if setRawTerminal && cli.isTerminal {
+ term.RestoreTerminal(cli.terminalFd, oldState)
+ }
+ // For some reason this Close call blocks on darwin..
+ // As the client exists right after, simply discard the close
+ // until we find a better solution.
+ if goruntime.GOOS != "darwin" {
+ in.Close()
+ }
+ }
+ }()
+
+ // When TTY is ON, use regular copy
+ if setRawTerminal {
+ _, err = io.Copy(stdout, br)
+ } else {
+ _, err = utils.StdCopy(stdout, stderr, br)
+ }
+ utils.Debugf("[hijack] End of stdout")
+ return err
+ })
+ }
+
+ sendStdin := utils.Go(func() error {
+ if in != nil {
+ io.Copy(rwc, in)
+ utils.Debugf("[hijack] End of stdin")
+ }
+ if tcpc, ok := rwc.(*net.TCPConn); ok {
+ if err := tcpc.CloseWrite(); err != nil {
+ utils.Debugf("Couldn't send EOF: %s\n", err)
+ }
+ } else if unixc, ok := rwc.(*net.UnixConn); ok {
+ if err := unixc.CloseWrite(); err != nil {
+ utils.Debugf("Couldn't send EOF: %s\n", err)
+ }
+ }
+ // Discard errors due to pipe interruption
+ return nil
+ })
+
+ if stdout != nil || stderr != nil {
+ if err := <-receiveStdout; err != nil {
+ utils.Debugf("Error receiveStdout: %s", err)
+ return err
+ }
+ }
+
+ if !cli.isTerminal {
+ if err := <-sendStdin; err != nil {
+ utils.Debugf("Error sendStdin: %s", err)
+ return err
+ }
+ }
+ return nil
+
+}
+
+func (cli *DockerCli) resizeTty(id string) {
+ height, width := cli.getTtySize()
+ if height == 0 && width == 0 {
+ return
+ }
+ v := url.Values{}
+ v.Set("h", strconv.Itoa(height))
+ v.Set("w", strconv.Itoa(width))
+ if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
+ utils.Debugf("Error resize: %s", err)
+ }
+}
+
+func waitForExit(cli *DockerCli, containerId string) (int, error) {
+ stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false)
+ if err != nil {
+ return -1, err
+ }
+
+ var out engine.Env
+ if err := out.Decode(stream); err != nil {
+ return -1, err
+ }
+ return out.GetInt("StatusCode"), nil
+}
+
+// getExitCode perform an inspect on the container. It returns
+// the running state and the exit code.
+func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
+ body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false))
+ if err != nil {
+ // If we can't connect, then the daemon probably died.
+ if err != ErrConnectionRefused {
+ return false, -1, err
+ }
+ return false, -1, nil
+ }
+ c := &api.Container{}
+ if err := json.Unmarshal(body, c); err != nil {
+ return false, -1, err
+ }
+ return c.State.Running, c.State.ExitCode, nil
+}
+
+func (cli *DockerCli) monitorTtySize(id string) error {
+ cli.resizeTty(id)
+
+ sigchan := make(chan os.Signal, 1)
+ gosignal.Notify(sigchan, syscall.SIGWINCH)
+ go func() {
+ for _ = range sigchan {
+ cli.resizeTty(id)
+ }
+ }()
+ return nil
+}
+
+func (cli *DockerCli) getTtySize() (int, int) {
+ if !cli.isTerminal {
+ return 0, 0
+ }
+ ws, err := term.GetWinsize(cli.terminalFd)
+ if err != nil {
+ utils.Debugf("Error getting size: %s", err)
+ if ws == nil {
+ return 0, 0
+ }
+ }
+ return int(ws.Height), int(ws.Width)
+}
+
+func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) {
+ if stream != nil {
+ defer stream.Close()
+ }
+ if err != nil {
+ return nil, statusCode, err
+ }
+ body, err := ioutil.ReadAll(stream)
+ if err != nil {
+ return nil, -1, err
+ }
+ return body, statusCode, nil
+}
diff --git a/api/common.go b/api/common.go
index 10e7ddb4ae..44bd901379 100644
--- a/api/common.go
+++ b/api/common.go
@@ -3,15 +3,16 @@ package api
import (
"fmt"
"github.com/dotcloud/docker/engine"
+ "github.com/dotcloud/docker/pkg/version"
"github.com/dotcloud/docker/utils"
"mime"
"strings"
)
const (
- APIVERSION = "1.10"
- DEFAULTHTTPHOST = "127.0.0.1"
- DEFAULTUNIXSOCKET = "/var/run/docker.sock"
+ APIVERSION version.Version = "1.10"
+ DEFAULTHTTPHOST = "127.0.0.1"
+ DEFAULTUNIXSOCKET = "/var/run/docker.sock"
)
func ValidateHost(val string) (string, error) {
@@ -23,8 +24,10 @@ func ValidateHost(val string) (string, error) {
}
//TODO remove, used on < 1.5 in getContainersJSON
-func displayablePorts(ports *engine.Table) string {
+func DisplayablePorts(ports *engine.Table) string {
result := []string{}
+ ports.SetKey("PublicPort")
+ ports.Sort()
for _, port := range ports.Data {
if port.Get("IP") == "" {
result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
diff --git a/api/server.go b/api/server/server.go
index 6fafe60f9f..c6eafaf265 100644
--- a/api/server.go
+++ b/api/server/server.go
@@ -1,21 +1,15 @@
-package api
+package server
import (
"bufio"
"bytes"
"code.google.com/p/go.net/websocket"
+ "crypto/tls"
+ "crypto/x509"
"encoding/base64"
"encoding/json"
"expvar"
"fmt"
- "github.com/dotcloud/docker/auth"
- "github.com/dotcloud/docker/engine"
- "github.com/dotcloud/docker/pkg/listenbuffer"
- "github.com/dotcloud/docker/pkg/systemd"
- "github.com/dotcloud/docker/pkg/user"
- "github.com/dotcloud/docker/pkg/version"
- "github.com/dotcloud/docker/utils"
- "github.com/gorilla/mux"
"io"
"io/ioutil"
"log"
@@ -26,7 +20,16 @@ import (
"strconv"
"strings"
"syscall"
- "time"
+
+ "github.com/dotcloud/docker/api"
+ "github.com/dotcloud/docker/engine"
+ "github.com/dotcloud/docker/pkg/listenbuffer"
+ "github.com/dotcloud/docker/pkg/systemd"
+ "github.com/dotcloud/docker/pkg/user"
+ "github.com/dotcloud/docker/pkg/version"
+ "github.com/dotcloud/docker/registry"
+ "github.com/dotcloud/docker/utils"
+ "github.com/gorilla/mux"
)
var (
@@ -314,7 +317,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo
for _, out := range outs.Data {
ports := engine.NewTable("", 0)
ports.ReadListFrom([]byte(out.Get("Ports")))
- out.Set("Ports", displayablePorts(ports))
+ out.Set("Ports", api.DisplayablePorts(ports))
}
w.Header().Set("Content-Type", "application/json")
if _, err = outs.WriteListTo(w); err != nil {
@@ -381,13 +384,13 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon
job *engine.Job
)
authEncoded := r.Header.Get("X-Registry-Auth")
- authConfig := &auth.AuthConfig{}
+ authConfig := &registry.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{}
+ authConfig = &registry.AuthConfig{}
}
}
if image != "" { //pull
@@ -429,7 +432,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
}
var (
authEncoded = r.Header.Get("X-Registry-Auth")
- authConfig = &auth.AuthConfig{}
+ authConfig = &registry.AuthConfig{}
metaHeaders = map[string][]string{}
)
@@ -438,7 +441,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
// for a search 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{}
+ authConfig = &registry.AuthConfig{}
}
}
for k, v := range r.Header {
@@ -455,6 +458,7 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons
return job.Run()
}
+// FIXME: 'insert' is deprecated as of 0.10, and should be removed in a future version.
func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return err
@@ -494,7 +498,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
if err := parseForm(r); err != nil {
return err
}
- authConfig := &auth.AuthConfig{}
+ authConfig := &registry.AuthConfig{}
authEncoded := r.Header.Get("X-Registry-Auth")
if authEncoded != "" {
@@ -502,7 +506,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
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{}
+ authConfig = &registry.AuthConfig{}
}
} else {
// the old format is supported for compatibility if there was no authConfig header
@@ -514,6 +518,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response
job := eng.Job("push", vars["name"])
job.SetenvJson("metaHeaders", metaHeaders)
job.SetenvJson("authConfig", authConfig)
+ job.Setenv("tag", r.Form.Get("tag"))
if version.GreaterThan("1.0") {
job.SetenvBool("json", true)
streamJSON(job, w, true)
@@ -624,6 +629,7 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr
var job = eng.Job("image_delete", vars["name"])
streamJSON(job, w, false)
job.Setenv("force", r.Form.Get("force"))
+ job.Setenv("noprune", r.Form.Get("noprune"))
return job.Run()
}
@@ -636,7 +642,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
job := eng.Job("start", name)
// allow a nil body for backwards compatibility
if r.Body != nil {
- if MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
+ if api.MatchesContentType(r.Header.Get("Content-Type"), "application/json") {
if err := job.DecodeEnv(r.Body); err != nil {
return err
}
@@ -823,9 +829,9 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
}
var (
authEncoded = r.Header.Get("X-Registry-Auth")
- authConfig = &auth.AuthConfig{}
+ authConfig = &registry.AuthConfig{}
configFileEncoded = r.Header.Get("X-Registry-Config")
- configFile = &auth.ConfigFile{}
+ configFile = &registry.ConfigFile{}
job = eng.Job("build")
)
@@ -838,7 +844,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
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{}
+ authConfig = &registry.AuthConfig{}
}
}
@@ -847,7 +853,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
if err := json.NewDecoder(configFileJson).Decode(configFile); 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
- configFile = &auth.ConfigFile{}
+ configFile = &registry.ConfigFile{}
}
}
@@ -883,7 +889,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
var copyData engine.Env
- if contentType := r.Header.Get("Content-Type"); contentType == "application/json" {
+ if contentType := r.Header.Get("Content-Type"); api.MatchesContentType(contentType, "application/json") {
if err := copyData.Decode(r.Body); err != nil {
return err
}
@@ -894,6 +900,9 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
if copyData.Get("Resource") == "" {
return fmt.Errorf("Path cannot be empty")
}
+
+ origResource := copyData.Get("Resource")
+
if copyData.Get("Resource")[0] == '/' {
copyData.Set("Resource", copyData.Get("Resource")[1:])
}
@@ -904,6 +913,8 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
utils.Errorf("%s", err.Error())
if strings.Contains(err.Error(), "No such container") {
w.WriteHeader(http.StatusNotFound)
+ } else if strings.Contains(err.Error(), "no such file or directory") {
+ return fmt.Errorf("Could not find the file %s in container %s", origResource, vars["name"])
}
}
return nil
@@ -930,20 +941,20 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
- if len(userAgent) == 2 && !dockerVersion.Equal(userAgent[1]) {
+ if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
}
}
version := version.Version(mux.Vars(r)["version"])
if version == "" {
- version = APIVERSION
+ version = api.APIVERSION
}
if enableCors {
writeCorsHeaders(w, r)
}
- if version.GreaterThan(APIVERSION) {
- http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound)
+ if version.GreaterThan(api.APIVERSION) {
+ http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, api.APIVERSION).Error(), http.StatusNotFound)
return
}
@@ -1130,9 +1141,8 @@ func changeGroup(addr string, nameOrGid string) error {
// ListenAndServe sets up the required http.Server and gets it listening for
// each addr passed in and does protocol specific checking.
-func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string, socketGroup string) error {
- r, err := createRouter(eng, logging, enableCors, dockerVersion)
-
+func ListenAndServe(proto, addr string, job *engine.Job) error {
+ r, err := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"))
if err != nil {
return err
}
@@ -1147,22 +1157,48 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors
}
}
- l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock, 15*time.Minute)
+ l, err := listenbuffer.NewListenBuffer(proto, addr, activationLock)
if err != nil {
return err
}
+ if proto != "unix" && (job.GetenvBool("Tls") || job.GetenvBool("TlsVerify")) {
+ tlsCert := job.Getenv("TlsCert")
+ tlsKey := job.Getenv("TlsKey")
+ cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey)
+ if err != nil {
+ return fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?",
+ tlsCert, tlsKey, err)
+ }
+ tlsConfig := &tls.Config{
+ NextProtos: []string{"http/1.1"},
+ Certificates: []tls.Certificate{cert},
+ }
+ if job.GetenvBool("TlsVerify") {
+ certPool := x509.NewCertPool()
+ file, err := ioutil.ReadFile(job.Getenv("TlsCa"))
+ if err != nil {
+ return fmt.Errorf("Couldn't read CA certificate: %s", err)
+ }
+ certPool.AppendCertsFromPEM(file)
+
+ tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
+ tlsConfig.ClientCAs = certPool
+ }
+ l = tls.NewListener(l, tlsConfig)
+ }
+
// Basic error and sanity checking
switch proto {
case "tcp":
- if !strings.HasPrefix(addr, "127.0.0.1") {
+ if !strings.HasPrefix(addr, "127.0.0.1") && !job.GetenvBool("TlsVerify") {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
case "unix":
if err := os.Chmod(addr, 0660); err != nil {
return err
}
-
+ socketGroup := job.Getenv("SocketGroup")
if socketGroup != "" {
if err := changeGroup(addr, socketGroup); err != nil {
if socketGroup == "docker" {
@@ -1198,7 +1234,7 @@ func ServeApi(job *engine.Job) engine.Status {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
go func() {
log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1])
- chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version"), job.Getenv("SocketGroup"))
+ chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job)
}()
}
diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go
new file mode 100644
index 0000000000..3dbba640ff
--- /dev/null
+++ b/api/server/server_unit_test.go
@@ -0,0 +1,180 @@
+package server
+
+import (
+ "fmt"
+ "github.com/dotcloud/docker/api"
+ "github.com/dotcloud/docker/engine"
+ "github.com/dotcloud/docker/utils"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "testing"
+)
+
+func TestGetBoolParam(t *testing.T) {
+ if ret, err := getBoolParam("true"); err != nil || !ret {
+ t.Fatalf("true -> true, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam("True"); err != nil || !ret {
+ t.Fatalf("True -> true, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam("1"); err != nil || !ret {
+ t.Fatalf("1 -> true, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam(""); err != nil || ret {
+ t.Fatalf("\"\" -> false, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam("false"); err != nil || ret {
+ t.Fatalf("false -> false, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam("0"); err != nil || ret {
+ t.Fatalf("0 -> false, nil | got %t %s", ret, err)
+ }
+ if ret, err := getBoolParam("faux"); err == nil || ret {
+ t.Fatalf("faux -> false, err | got %t %s", ret, err)
+
+ }
+}
+
+func TesthttpError(t *testing.T) {
+ r := httptest.NewRecorder()
+
+ httpError(r, fmt.Errorf("No such method"))
+ if r.Code != http.StatusNotFound {
+ t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code)
+ }
+
+ httpError(r, fmt.Errorf("This accound hasn't been activated"))
+ if r.Code != http.StatusForbidden {
+ t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code)
+ }
+
+ httpError(r, fmt.Errorf("Some error"))
+ if r.Code != http.StatusInternalServerError {
+ t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code)
+ }
+}
+
+func TestGetVersion(t *testing.T) {
+ tmp, err := utils.TestDirectory("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
+ eng, err := engine.New(tmp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var called bool
+ eng.Register("version", func(job *engine.Job) engine.Status {
+ called = true
+ v := &engine.Env{}
+ v.SetJson("Version", "42.1")
+ v.Set("ApiVersion", "1.1.1.1.1")
+ v.Set("GoVersion", "2.42")
+ v.Set("Os", "Linux")
+ v.Set("Arch", "x86_64")
+ if _, err := v.WriteTo(job.Stdout); err != nil {
+ return job.Error(err)
+ }
+ return engine.StatusOK
+ })
+
+ r := httptest.NewRecorder()
+ req, err := http.NewRequest("GET", "/version", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // FIXME getting the version should require an actual running Server
+ if err := ServeRequest(eng, api.APIVERSION, r, req); err != nil {
+ t.Fatal(err)
+ }
+ if !called {
+ t.Fatalf("handler was not called")
+ }
+ out := engine.NewOutput()
+ v, err := out.AddEnv()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := io.Copy(out, r.Body); err != nil {
+ t.Fatal(err)
+ }
+ out.Close()
+ expected := "42.1"
+ if result := v.Get("Version"); result != expected {
+ t.Errorf("Expected version %s, %s found", expected, result)
+ }
+ expected = "application/json"
+ if result := r.HeaderMap.Get("Content-Type"); result != expected {
+ t.Errorf("Expected Content-Type %s, %s found", expected, result)
+ }
+}
+
+func TestGetInfo(t *testing.T) {
+ tmp, err := utils.TestDirectory("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
+ eng, err := engine.New(tmp)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var called bool
+ eng.Register("info", func(job *engine.Job) engine.Status {
+ called = true
+ v := &engine.Env{}
+ v.SetInt("Containers", 1)
+ v.SetInt("Images", 42000)
+ if _, err := v.WriteTo(job.Stdout); err != nil {
+ return job.Error(err)
+ }
+ return engine.StatusOK
+ })
+
+ r := httptest.NewRecorder()
+ req, err := http.NewRequest("GET", "/info", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // FIXME getting the version should require an actual running Server
+ if err := ServeRequest(eng, api.APIVERSION, r, req); err != nil {
+ t.Fatal(err)
+ }
+ if !called {
+ t.Fatalf("handler was not called")
+ }
+
+ out := engine.NewOutput()
+ i, err := out.AddEnv()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := io.Copy(out, r.Body); err != nil {
+ t.Fatal(err)
+ }
+ out.Close()
+ {
+ expected := 42000
+ result := i.GetInt("Images")
+ if expected != result {
+ t.Fatalf("%#v\n", result)
+ }
+ }
+ {
+ expected := 1
+ result := i.GetInt("Containers")
+ if expected != result {
+ t.Fatalf("%#v\n", result)
+ }
+ }
+ {
+ expected := "application/json"
+ if result := r.HeaderMap.Get("Content-Type"); result != expected {
+ t.Fatalf("%#v\n", result)
+ }
+ }
+}